Code style fixes and nits on the HLE project (#355)

* Some style fixes and nits on ITimeZoneService

* Remove some unneeded usings

* Remove the Ryujinx.HLE.OsHle.Handles namespace

* Remove hbmenu automatic load on process exit

* Rename Ns to Device, rename Os to System, rename SystemState to State

* Move Exceptions and Utilities out of OsHle

* Rename OsHle to HOS

* Rename OsHle folder to HOS

* IManagerDisplayService and ISystemDisplayService style fixes

* BsdError shouldn't be public

* Add a empty new line before using static

* Remove unused file

* Some style fixes on NPDM

* Exit gracefully when the application is closed

* Code style fixes on IGeneralService

* Add 0x prefix on values printed as hex

* Small improvements on finalization code

* Move ProcessId and ThreadId out of AThreadState

* Rename VFs to FileSystem

* FsAccessHeader shouldn't be public. Also fix file names casing

* More case changes on NPDM

* Remove unused files

* Move using to the correct place on NPDM

* Use properties on KernelAccessControlMmio

* Address PR feedback
This commit is contained in:
gdkchan 2018-08-16 20:47:36 -03:00 committed by GitHub
parent 182d716867
commit 521751795a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
258 changed files with 1574 additions and 1546 deletions

View file

@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.HOS.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.HOS
{
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.HOS
{
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,122 @@
using Ryujinx.HLE.Memory;
using Ryujinx.HLE.Resource;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.HOS.Font
{
class SharedFontManager
{
private DeviceMemory Memory;
private long PhysicalAddress;
private string FontsPath;
private struct FontInfo
{
public int Offset;
public int Size;
public FontInfo(int Offset, int Size)
{
this.Offset = Offset;
this.Size = Size;
}
}
private Dictionary<SharedFontType, FontInfo> FontData;
public SharedFontManager(Switch Device, long PhysicalAddress)
{
this.PhysicalAddress = PhysicalAddress;
Memory = Device.Memory;
FontsPath = Path.Combine(Device.FileSystem.GetSystemPath(), "fonts");
}
public void EnsureInitialized()
{
if (FontData == null)
{
Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
uint FontOffset = 0;
FontInfo CreateFont(string Name)
{
string FontFilePath = Path.Combine(FontsPath, Name + ".ttf");
if (File.Exists(FontFilePath))
{
byte[] Data = File.ReadAllBytes(FontFilePath);
FontInfo Info = new FontInfo((int)FontOffset, Data.Length);
WriteMagicAndSize(PhysicalAddress + FontOffset, Data.Length);
FontOffset += 8;
uint Start = FontOffset;
for (; FontOffset - Start < Data.Length; FontOffset++)
{
Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
}
return Info;
}
else
{
throw new InvalidSystemResourceException($"Font \"{Name}.ttf\" not found. Please provide it in \"{FontsPath}\".");
}
}
FontData = new Dictionary<SharedFontType, FontInfo>()
{
{ SharedFontType.JapanUsEurope, CreateFont("FontStandard") },
{ SharedFontType.SimplifiedChinese, CreateFont("FontChineseSimplified") },
{ SharedFontType.SimplifiedChineseEx, CreateFont("FontExtendedChineseSimplified") },
{ SharedFontType.TraditionalChinese, CreateFont("FontChineseTraditional") },
{ SharedFontType.Korean, CreateFont("FontKorean") },
{ SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") }
};
if (FontOffset > Horizon.FontSize)
{
throw new InvalidSystemResourceException(
$"The sum of all fonts size exceed the shared memory size. " +
$"Please make sure that the fonts don't exceed {Horizon.FontSize} bytes in total. " +
$"(actual size: {FontOffset} bytes).");
}
}
}
private void WriteMagicAndSize(long Position, int Size)
{
const int DecMagic = 0x18029a7f;
const int Key = 0x49621806;
int EncryptedSize = EndianSwap.Swap32(Size ^ Key);
Memory.WriteInt32(Position + 0, DecMagic);
Memory.WriteInt32(Position + 4, EncryptedSize);
}
public int GetFontSize(SharedFontType FontType)
{
EnsureInitialized();
return FontData[FontType].Size;
}
public int GetSharedMemoryAddressOffset(SharedFontType FontType)
{
EnsureInitialized();
return FontData[FontType].Offset + 8;
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Font
{
public enum SharedFontType
{
JapanUsEurope = 0,
SimplifiedChinese = 1,
SimplifiedChineseEx = 2,
TraditionalChinese = 3,
Korean = 4,
NintendoEx = 5,
Count
}
}

View file

@ -0,0 +1,69 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS
{
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,77 @@
using ChocolArm64.Memory;
using System.Text;
namespace Ryujinx.HLE.HOS
{
static class Homebrew
{
public const string TemporaryNroSuffix = ".ryu_tmp.nro";
//http://switchbrew.org/index.php?title=Homebrew_ABI
public static void WriteHbAbiData(AMemory Memory, long Position, int MainThreadHandle, string SwitchPath)
{
//MainThreadHandle.
WriteConfigEntry(Memory, ref Position, 1, 0, MainThreadHandle);
//NextLoadPath.
WriteConfigEntry(Memory, ref Position, 2, 0, Position + 0x200, Position + 0x400);
//Argv.
long ArgvPosition = Position + 0xC00;
Memory.WriteBytes(ArgvPosition, Encoding.ASCII.GetBytes(SwitchPath + "\0"));
WriteConfigEntry(Memory, ref Position, 5, 0, 0, ArgvPosition);
//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;
}
}
}

227
Ryujinx.HLE/HOS/Horizon.cs Normal file
View file

@ -0,0 +1,227 @@
using Ryujinx.HLE.HOS.Font;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Ryujinx.HLE.HOS
{
public class Horizon : IDisposable
{
internal const int HidSize = 0x40000;
internal const int FontSize = 0x1100000;
private Switch Device;
private KProcessScheduler Scheduler;
private ConcurrentDictionary<int, Process> Processes;
public SystemStateMgr State { get; private set; }
internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; }
internal SharedFontManager Font { get; private set; }
internal KEvent VsyncEvent { get; private set; }
public Horizon(Switch Device)
{
this.Device = Device;
Scheduler = new KProcessScheduler(Device.Log);
Processes = new ConcurrentDictionary<int, Process>();
State = new SystemStateMgr();
if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) ||
!Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA))
{
throw new InvalidOperationException();
}
HidSharedMem = new KSharedMemory(HidPA, HidSize);
FontSharedMem = new KSharedMemory(FontPA, FontSize);
Font = new SharedFontManager(Device, FontSharedMem.PA);
VsyncEvent = new KEvent();
}
public void LoadCart(string ExeFsDir, string RomFsFile = null)
{
if (RomFsFile != null)
{
Device.FileSystem.LoadRomFs(RomFsFile);
}
string NpdmFileName = Path.Combine(ExeFsDir, "main.npdm");
Npdm MetaData = null;
if (File.Exists(NpdmFileName))
{
Device.Log.PrintInfo(LogClass.Loader, $"Loading main.npdm...");
using (FileStream Input = new FileStream(NpdmFileName, FileMode.Open))
{
MetaData = new Npdm(Input);
}
}
else
{
Device.Log.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
}
Process MainProcess = MakeProcess(MetaData);
void LoadNso(string FileName)
{
foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
{
if (Path.GetExtension(File) != string.Empty)
{
continue;
}
Device.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);
}
}
}
if (!MainProcess.MetaData.Is64Bits)
{
throw new NotImplementedException("32-bit titles are unsupported!");
}
LoadNso("rtld");
MainProcess.SetEmptyArgs();
LoadNso("main");
LoadNso("subsdk*");
LoadNso("sdk");
MainProcess.Run();
}
public void LoadProgram(string FilePath)
{
bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
string Name = Path.GetFileNameWithoutExtension(FilePath);
string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
{
string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
string SwitchDir = Path.GetDirectoryName(TempPath);
if (!Directory.Exists(SwitchDir))
{
Directory.CreateDirectory(SwitchDir);
}
File.Copy(FilePath, TempPath, true);
FilePath = TempPath;
}
Process MainProcess = MakeProcess();
using (FileStream Input = new FileStream(FilePath, FileMode.Open))
{
MainProcess.LoadProgram(IsNro
? (IExecutable)new Nro(Input, FilePath)
: (IExecutable)new Nso(Input, FilePath));
}
MainProcess.SetEmptyArgs();
MainProcess.Run(IsNro);
}
public void SignalVsync() => VsyncEvent.WaitEvent.Set();
private Process MakeProcess(Npdm MetaData = null)
{
Process Process;
lock (Processes)
{
int ProcessId = 0;
while (Processes.ContainsKey(ProcessId))
{
ProcessId++;
}
Process = new Process(Device, Scheduler, ProcessId, MetaData);
Processes.TryAdd(ProcessId, Process);
}
InitializeProcess(Process);
return Process;
}
private void InitializeProcess(Process Process)
{
Process.AppletState.SetFocus(true);
}
internal void ExitProcess(int ProcessId)
{
if (Processes.TryRemove(ProcessId, out Process Process))
{
Process.Dispose();
if (Processes.Count == 0)
{
Unload();
Device.Unload();
}
}
}
private void Unload()
{
VsyncEvent.Dispose();
Scheduler.Dispose();
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
foreach (Process Process in Processes.Values)
{
Process.Dispose();
}
}
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS
{
class IdDictionary
{
private ConcurrentDictionary<int, object> Objs;
public IdDictionary()
{
Objs = new ConcurrentDictionary<int, object>();
}
public bool Add(int Id, object Data)
{
return Objs.TryAdd(Id, Data);
}
public int Add(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))
{
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.HOS.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,92 @@
using System;
using System.IO;
namespace Ryujinx.HLE.HOS.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(params int[] Handles)
{
return new IpcHandleDesc(Handles, new int[0]);
}
public static IpcHandleDesc MakeMove(params int[] Handles)
{
return new IpcHandleDesc(new int[0], Handles);
}
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,140 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.HOS.Kernel;
using System;
using System.IO;
namespace Ryujinx.HLE.HOS.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 ||
Request.Type == IpcMessageType.RequestWithContext)
{
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 ||
Request.Type == IpcMessageType.ControlWithContext)
{
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.HOS.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.HOS.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> ObjectIds { 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>();
ObjectIds = 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,12 @@
namespace Ryujinx.HLE.HOS.Ipc
{
enum IpcMessageType
{
Response = 0,
CloseSession = 2,
Request = 4,
Control = 5,
RequestWithContext = 6,
ControlWithContext = 7
}
}

View file

@ -0,0 +1,26 @@
using System.IO;
namespace Ryujinx.HLE.HOS.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.HOS.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.HOS.Ipc
{
delegate long ServiceProcessRequest(ServiceCtx Context);
}

View file

@ -0,0 +1,111 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Kernel
{
static class AddressArbiter
{
static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout)
{
KThread CurrentThread = Process.GetThread(ThreadState.Tpidr);
Process.Scheduler.SetReschedule(CurrentThread.ProcessorId);
CurrentThread.ArbiterWaitAddress = Address;
CurrentThread.ArbiterSignaled = false;
Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout));
if (!CurrentThread.ArbiterSignaled)
{
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
}
return 0;
}
public static ulong WaitForAddressIfLessThan(Process Process,
AThreadState ThreadState,
AMemory Memory,
long Address,
int Value,
ulong Timeout,
bool ShouldDecrement)
{
Memory.SetExclusive(ThreadState, Address);
int CurrentValue = Memory.ReadInt32(Address);
while (true)
{
if (Memory.TestExclusive(ThreadState, Address))
{
if (CurrentValue < Value)
{
if (ShouldDecrement)
{
Memory.WriteInt32(Address, CurrentValue - 1);
}
Memory.ClearExclusiveForStore(ThreadState);
}
else
{
Memory.ClearExclusiveForStore(ThreadState);
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
}
break;
}
Memory.SetExclusive(ThreadState, Address);
CurrentValue = Memory.ReadInt32(Address);
}
if (Timeout == 0)
{
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
}
return WaitForAddress(Process, ThreadState, Address, Timeout);
}
public static ulong WaitForAddressIfEqual(Process Process,
AThreadState ThreadState,
AMemory Memory,
long Address,
int Value,
ulong Timeout)
{
if (Memory.ReadInt32(Address) != Value)
{
return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
}
if (Timeout == 0)
{
return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
}
return WaitForAddress(Process, ThreadState, Address, Timeout);
}
}
enum ArbitrationType : int
{
WaitIfLessThan,
DecrementAndWaitIfLessThan,
WaitIfEqual
}
enum SignalType : int
{
Signal,
IncrementAndSignalIfEqual,
ModifyByWaitingCountAndSignalIfEqual
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Kernel
{
enum AddressSpaceType
{
Addr32Bits = 0,
Addr36Bits = 1,
Addr36BitsNoMap = 2,
Addr39Bits = 3
}
}

View file

@ -0,0 +1,4 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KEvent : KSynchronizationObject { }
}

View file

@ -0,0 +1,43 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryBlock
{
public long BasePosition { get; set; }
public long PagesCount { get; set; }
public MemoryState State { get; set; }
public MemoryPermission Permission { get; set; }
public MemoryAttribute Attribute { get; set; }
public int IpcRefCount { get; set; }
public int DeviceRefCount { get; set; }
public KMemoryBlock(
long BasePosition,
long PagesCount,
MemoryState State,
MemoryPermission Permission,
MemoryAttribute Attribute)
{
this.BasePosition = BasePosition;
this.PagesCount = PagesCount;
this.State = State;
this.Attribute = Attribute;
this.Permission = Permission;
}
public KMemoryInfo GetInfo()
{
long Size = PagesCount * KMemoryManager.PageSize;
return new KMemoryInfo(
BasePosition,
Size,
State,
Permission,
Attribute,
IpcRefCount,
DeviceRefCount);
}
}
}

View file

@ -0,0 +1,33 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryInfo
{
public long Position { get; private set; }
public long Size { get; private set; }
public MemoryState State { get; private set; }
public MemoryPermission Permission { get; private set; }
public MemoryAttribute Attribute { get; private set; }
public int IpcRefCount { get; private set; }
public int DeviceRefCount { get; private set; }
public KMemoryInfo(
long Position,
long Size,
MemoryState State,
MemoryPermission Permission,
MemoryAttribute Attribute,
int IpcRefCount,
int DeviceRefCount)
{
this.Position = Position;
this.Size = Size;
this.State = State;
this.Attribute = Attribute;
this.Permission = Permission;
this.IpcRefCount = IpcRefCount;
this.DeviceRefCount = DeviceRefCount;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Kernel
{
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,370 @@
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel
{
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)
{
SchedulerThread SchedThread = AllThreads[Thread];
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 ForceWakeUp(KThread Thread)
{
if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
{
SchedThread.WaitSync.Set();
SchedThread.WaitActivity.Set();
SchedThread.WaitSched.Set();
}
}
public void ChangeCore(KThread Thread, int IdealCore, int CoreMask)
{
lock (SchedLock)
{
if (IdealCore != -3)
{
Thread.IdealCore = IdealCore;
}
Thread.CoreMask = CoreMask;
if (AllThreads.ContainsKey(Thread))
{
SetReschedule(Thread.ActualCore);
SchedulerThread SchedThread = AllThreads[Thread];
//Note: Aways if the thread is on the queue first, and try
//adding to a new core later, to ensure that a thread that
//is already running won't be added to another core.
if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
{
WaitingToRun.Remove(SchedThread);
RunThread(SchedThread);
}
}
}
}
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)
{
NewThread.Thread.ActualCore = ActualCore;
CoreThreads[ActualCore] = NewThread.Thread;
RunThread(NewThread);
}
else
{
CoreThreads[ActualCore] = null;
}
}
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.HOS.Services;
using System;
namespace Ryujinx.HLE.HOS.Kernel
{
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,14 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KSharedMemory
{
public long PA { get; private set; }
public long Size { get; private set; }
public KSharedMemory(long PA, long Size)
{
this.PA = PA;
this.Size = Size;
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel
{
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,98 @@
using ChocolArm64;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Kernel
{
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 long ArbiterWaitAddress { get; set; }
public bool CondVarSignaled { get; set; }
public bool ArbiterSignaled { 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 long LastPc { get; set; }
public int ThreadId { get; private set; }
public KThread(
AThread Thread,
Process Process,
int ProcessorId,
int Priority,
int ThreadId)
{
this.Thread = Thread;
this.Process = Process;
this.ProcessorId = ProcessorId;
this.IdealCore = ProcessorId;
this.ThreadId = ThreadId;
MutexWaiters = new List<KThread>();
CoreMask = 1 << ProcessorId;
ActualPriority = WantedPriority = Priority;
}
public void SetPriority(int Priority)
{
WantedPriority = Priority;
UpdatePriority();
}
public void UpdatePriority()
{
bool PriorityChanged;
lock (Process.ThreadSyncLock)
{
int OldPriority = ActualPriority;
int CurrPriority = WantedPriority;
foreach (KThread Thread in MutexWaiters)
{
int WantedPriority = Thread.WantedPriority;
if (CurrPriority > WantedPriority)
{
CurrPriority = WantedPriority;
}
}
PriorityChanged = CurrPriority != OldPriority;
ActualPriority = CurrPriority;
}
if (PriorityChanged)
{
Process.Scheduler.Resort(this);
MutexOwner?.UpdatePriority();
}
}
}
}

View file

@ -0,0 +1,60 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel
{
class KTlsPageManager
{
private const int TlsEntrySize = 0x200;
private long PagePosition;
private int UsedSlots;
private bool[] Slots;
public bool IsEmpty => UsedSlots == 0;
public bool IsFull => UsedSlots == Slots.Length;
public KTlsPageManager(long PagePosition)
{
this.PagePosition = PagePosition;
Slots = new bool[KMemoryManager.PageSize / TlsEntrySize];
}
public bool TryGetFreeTlsAddr(out long Position)
{
Position = PagePosition;
for (int Index = 0; Index < Slots.Length; Index++)
{
if (!Slots[Index])
{
Slots[Index] = true;
UsedSlots++;
return true;
}
Position += TlsEntrySize;
}
Position = 0;
return false;
}
public void FreeTlsSlot(int Slot)
{
if ((uint)Slot > Slots.Length)
{
throw new ArgumentOutOfRangeException(nameof(Slot));
}
Slots[Slot] = false;
UsedSlots--;
}
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.HLE.HOS.Kernel
{
class KTransferMemory
{
public long Position { get; private set; }
public long Size { get; private set; }
public KTransferMemory(long Position, long Size)
{
this.Position = Position;
this.Size = Size;
}
}
}

View file

@ -0,0 +1,22 @@
namespace Ryujinx.HLE.HOS.Kernel
{
static class KernelErr
{
public const int InvalidSize = 101;
public const int InvalidAddress = 102;
public const int OutOfMemory = 104;
public const int NoAccessPerm = 106;
public const int InvalidPermission = 108;
public const int InvalidMemRange = 110;
public const int InvalidPriority = 112;
public const int InvalidCoreId = 113;
public const int InvalidHandle = 114;
public const int InvalidMaskValue = 116;
public const int Timeout = 117;
public const int Canceled = 118;
public const int CountOutOfRange = 119;
public const int InvalidEnumValue = 120;
public const int InvalidThread = 122;
public const int InvalidState = 125;
}
}

View file

@ -0,0 +1,22 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel
{
[Flags]
enum MemoryAttribute : byte
{
None = 0,
Mask = 0xff,
Borrowed = 1 << 0,
IpcMapped = 1 << 1,
DeviceMapped = 1 << 2,
Uncached = 1 << 3,
IpcAndDeviceMapped = IpcMapped | DeviceMapped,
BorrowedAndIpcMapped = Borrowed | IpcMapped,
DeviceMappedAndUncached = DeviceMapped | Uncached
}
}

View file

@ -0,0 +1,18 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel
{
[Flags]
enum MemoryPermission : byte
{
None = 0,
Mask = 0xff,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
ReadAndWrite = Read | Write,
ReadAndExecute = Read | Execute
}
}

View file

@ -0,0 +1,49 @@
using System;
namespace Ryujinx.HLE.HOS.Kernel
{
[Flags]
enum MemoryState : uint
{
Unmapped = 0x00000000,
Io = 0x00002001,
Normal = 0x00042002,
CodeStatic = 0x00DC7E03,
CodeMutable = 0x03FEBD04,
Heap = 0x037EBD05,
SharedMemory = 0x00402006,
ModCodeStatic = 0x00DD7E08,
ModCodeMutable = 0x03FFBD09,
IpcBuffer0 = 0x005C3C0A,
MappedMemory = 0x005C3C0B,
ThreadLocal = 0x0040200C,
TransferMemoryIsolated = 0x015C3C0D,
TransferMemory = 0x005C380E,
ProcessMemory = 0x0040380F,
Reserved = 0x00000010,
IpcBuffer1 = 0x005C3811,
IpcBuffer3 = 0x004C2812,
KernelStack = 0x00002013,
CodeReadOnly = 0x00402214,
CodeWritable = 0x00402015,
Mask = 0xffffffff,
PermissionChangeAllowed = 1 << 8,
ForceReadWritableByDebugSyscalls = 1 << 9,
IpcSendAllowedType0 = 1 << 10,
IpcSendAllowedType3 = 1 << 11,
IpcSendAllowedType1 = 1 << 12,
ProcessPermissionChangeAllowed = 1 << 14,
MapAllowed = 1 << 15,
UnmapProcessCodeMemoryAllowed = 1 << 16,
TransferMemoryAllowed = 1 << 17,
QueryPhysicalAddressAllowed = 1 << 18,
MapDeviceAllowed = 1 << 19,
MapDeviceAlignedAllowed = 1 << 20,
IpcBufferAllowed = 1 << 21,
IsPoolAllocated = 1 << 22,
MapProcessAllowed = 1 << 23,
AttributeChangeAllowed = 1 << 24,
CodeMemoryAllowed = 1 << 25
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.HLE.HOS.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,48 @@
using System;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel
{
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,123 @@
using ChocolArm64.Events;
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel
{
partial class SvcHandler
{
private delegate void SvcFunc(AThreadState ThreadState);
private Dictionary<int, SvcFunc> SvcFuncs;
private Switch Device;
private Process Process;
private AMemory Memory;
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
private const uint SelfThreadHandle = 0xffff8000;
private const uint SelfProcessHandle = 0xffff8001;
private static Random Rng;
public SvcHandler(Switch Device, 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 },
{ 0x33, SvcGetThreadContext3 },
{ 0x34, SvcWaitForAddress }
};
this.Device = Device;
this.Process = Process;
this.Memory = Process.Memory;
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
}
static SvcHandler()
{
Rng = new Random();
}
public void SvcCall(object sender, AInstExceptionEventArgs e)
{
AThreadState ThreadState = (AThreadState)sender;
Process.GetThread(ThreadState.Tpidr).LastPc = e.Position;
if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
{
Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
Func(ThreadState);
Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
}
else
{
Process.PrintStackTrace(ThreadState);
throw new NotImplementedException($"0x{e.Id:x4}");
}
}
private KThread GetThread(long Tpidr, int Handle)
{
if ((uint)Handle == SelfThreadHandle)
{
return Process.GetThread(Tpidr);
}
else
{
return Process.HandleTable.GetData<KThread>(Handle);
}
}
}
}

View file

@ -0,0 +1,577 @@
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Kernel
{
partial class SvcHandler
{
private void SvcSetHeapSize(AThreadState ThreadState)
{
long Size = (long)ThreadState.X1;
if ((Size & 0x1fffff) != 0 || Size != (uint)Size)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
long Result = Process.MemoryManager.TrySetHeapSize(Size, out long Position);
ThreadState.X0 = (ulong)Result;
if (Result == 0)
{
ThreadState.X1 = (ulong)Position;
}
else
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
}
private void SvcSetMemoryAttribute(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
MemoryAttribute AttributeMask = (MemoryAttribute)ThreadState.X2;
MemoryAttribute AttributeValue = (MemoryAttribute)ThreadState.X3;
MemoryAttribute Attributes = AttributeMask | AttributeValue;
if (Attributes != AttributeMask ||
(Attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
{
Device.Log.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
return;
}
long Result = Process.MemoryManager.SetMemoryAttribute(
Position,
Size,
AttributeMask,
AttributeValue);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
else
{
Memory.StopObservingRegion(Position, Size);
}
ThreadState.X0 = (ulong)Result;
}
private void SvcMapMemory(AThreadState ThreadState)
{
long Dst = (long)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!PageAligned(Src | Dst))
{
Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
{
Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideAddrSpace(Src, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideNewMapRegion(Dst, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
long Result = Process.MemoryManager.Map(Src, Dst, Size);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private void SvcUnmapMemory(AThreadState ThreadState)
{
long Dst = (long)ThreadState.X0;
long Src = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!PageAligned(Src | Dst))
{
Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
{
Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideAddrSpace(Src, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideNewMapRegion(Dst, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
return;
}
long Result = Process.MemoryManager.Unmap(Src, Dst, Size);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private void SvcQueryMemory(AThreadState ThreadState)
{
long InfoPtr = (long)ThreadState.X0;
long Position = (long)ThreadState.X2;
KMemoryInfo BlkInfo = Process.MemoryManager.QueryMemory(Position);
Memory.WriteInt64(InfoPtr + 0x00, BlkInfo.Position);
Memory.WriteInt64(InfoPtr + 0x08, BlkInfo.Size);
Memory.WriteInt32(InfoPtr + 0x10, (int)BlkInfo.State & 0xff);
Memory.WriteInt32(InfoPtr + 0x14, (int)BlkInfo.Attribute);
Memory.WriteInt32(InfoPtr + 0x18, (int)BlkInfo.Permission);
Memory.WriteInt32(InfoPtr + 0x1c, BlkInfo.IpcRefCount);
Memory.WriteInt32(InfoPtr + 0x20, BlkInfo.DeviceRefCount);
Memory.WriteInt32(InfoPtr + 0x24, 0);
ThreadState.X0 = 0;
ThreadState.X1 = 0;
}
private void SvcMapSharedMemory(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
long Position = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Position + Size) <= (ulong)Position)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
if ((Permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
return;
}
KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
if (SharedMemory == null)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (SharedMemory.Size != Size)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
long Result = Process.MemoryManager.MapSharedMemory(SharedMemory, Permission, Position);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private void SvcUnmapSharedMemory(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
long Position = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Position + Size) <= (ulong)Position)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
if (SharedMemory == null)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
long Result = Process.MemoryManager.UnmapSharedMemory(Position, Size);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private void SvcCreateTransferMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X1;
long Size = (long)ThreadState.X2;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if ((ulong)(Position + Size) <= (ulong)Position)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
if (Permission > MemoryPermission.ReadAndWrite || Permission == MemoryPermission.Write)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
return;
}
Process.MemoryManager.ReserveTransferMemory(Position, Size, Permission);
KTransferMemory TransferMemory = new KTransferMemory(Position, Size);
int Handle = Process.HandleTable.OpenHandle(TransferMemory);
ThreadState.X0 = 0;
ThreadState.X1 = (ulong)Handle;
}
private void SvcMapPhysicalMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Position + Size) <= (ulong)Position)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideAddrSpace(Position, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
long Result = Process.MemoryManager.MapPhysicalMemory(Position, Size);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
long Size = (long)ThreadState.X1;
if (!PageAligned(Position))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
if (!PageAligned(Size) || Size == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
return;
}
if ((ulong)(Position + Size) <= (ulong)Position)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (!InsideAddrSpace(Position, Size))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
long Result = Process.MemoryManager.UnmapPhysicalMemory(Position, Size);
if (Result != 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
}
ThreadState.X0 = (ulong)Result;
}
private static bool PageAligned(long Position)
{
return (Position & (KMemoryManager.PageSize - 1)) == 0;
}
private bool InsideAddrSpace(long Position, long Size)
{
ulong Start = (ulong)Position;
ulong End = (ulong)Size + Start;
return Start >= (ulong)Process.MemoryManager.AddrSpaceStart &&
End < (ulong)Process.MemoryManager.AddrSpaceEnd;
}
private bool InsideMapRegion(long Position, long Size)
{
ulong Start = (ulong)Position;
ulong End = (ulong)Size + Start;
return Start >= (ulong)Process.MemoryManager.MapRegionStart &&
End < (ulong)Process.MemoryManager.MapRegionEnd;
}
private bool InsideHeapRegion(long Position, long Size)
{
ulong Start = (ulong)Position;
ulong End = (ulong)Size + Start;
return Start >= (ulong)Process.MemoryManager.HeapRegionStart &&
End < (ulong)Process.MemoryManager.HeapRegionEnd;
}
private bool InsideNewMapRegion(long Position, long Size)
{
ulong Start = (ulong)Position;
ulong End = (ulong)Size + Start;
return Start >= (ulong)Process.MemoryManager.NewMapRegionStart &&
End < (ulong)Process.MemoryManager.NewMapRegionEnd;
}
}
}

View file

@ -0,0 +1,373 @@
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.Logging;
using System;
using System.Threading;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Kernel
{
partial class SvcHandler
{
private const int AllowedCpuIdBitmask = 0b1111;
private const bool EnableProcessDebugging = false;
private void SvcExitProcess(AThreadState ThreadState)
{
Device.System.ExitProcess(Process.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)
{
Device.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 KTransferMemory TransferMemory)
{
Process.MemoryManager.ResetTransferMemory(
TransferMemory.Position,
TransferMemory.Size);
}
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
{
Device.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;
Device.Log.PrintDebug(LogClass.KernelSvc,
"HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " +
"HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " +
"Timeout = 0x" + 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)
{
Device.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)
{
Device.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(Device, Process, Memory, Session, Cmd, CmdPtr);
Thread.Yield();
Process.Scheduler.Resume(CurrThread);
ThreadState.X0 = (ulong)Result;
}
else
{
Device.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);
Device.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.InvalidEnumValue);
return;
}
switch (InfoType)
{
case 0:
ThreadState.X1 = AllowedCpuIdBitmask;
break;
case 2:
ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionStart;
break;
case 3:
ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionEnd -
(ulong)Process.MemoryManager.MapRegionStart;
break;
case 4:
ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionStart;
break;
case 5:
ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionEnd -
(ulong)Process.MemoryManager.HeapRegionStart;
break;
case 6:
ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalAvailableSize;
break;
case 7:
ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalUsedSize;
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 = (ulong)Process.MemoryManager.AddrSpaceStart;
break;
case 13:
ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceEnd -
(ulong)Process.MemoryManager.AddrSpaceStart;
break;
case 14:
ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionStart;
break;
case 15:
ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionEnd -
(ulong)Process.MemoryManager.NewMapRegionStart;
break;
case 16:
ThreadState.X1 = (ulong)(Process.MetaData?.SystemResourceSize ?? 0);
break;
case 17:
ThreadState.X1 = (ulong)Process.MemoryManager.PersonalMmHeapUsage;
break;
default:
Process.PrintStackTrace(ThreadState);
throw new NotImplementedException($"SvcGetInfo: {InfoType} 0x{Handle:x8} {InfoId}");
}
ThreadState.X0 = 0;
}
}
}

View file

@ -0,0 +1,385 @@
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using System.Threading;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.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)
{
Device.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)
{
Device.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
{
Device.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;
Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + TimeoutNs.ToString("x16"));
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
if (TimeoutNs == 0 || TimeoutNs == ulong.MaxValue)
{
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
{
Device.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;
Device.Log.PrintDebug(LogClass.KernelSvc,
"Handle = 0x" + Handle .ToString("x8") + ", " +
"Priority = 0x" + Priority.ToString("x8"));
KThread Thread = GetThread(ThreadState.Tpidr, Handle);
if (Thread != null)
{
Thread.SetPriority(Priority);
ThreadState.X0 = 0;
}
else
{
Device.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;
Device.Log.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + 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
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcSetThreadCoreMask(AThreadState ThreadState)
{
int Handle = (int)ThreadState.X0;
int IdealCore = (int)ThreadState.X1;
long CoreMask = (long)ThreadState.X2;
Device.Log.PrintDebug(LogClass.KernelSvc,
"Handle = 0x" + Handle .ToString("x8") + ", " +
"IdealCore = 0x" + IdealCore.ToString("x8") + ", " +
"CoreMask = 0x" + 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 ((uint)IdealCore > 3)
{
if ((IdealCore | 2) != -1)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
return;
}
}
else if ((CoreMask & (1 << IdealCore)) == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
return;
}
}
if (Thread == null)
{
Device.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 && (CoreMask & (1 << Thread.IdealCore)) == 0)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
return;
}
Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask);
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
{
Device.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
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
}
}
private void SvcGetThreadContext3(AThreadState ThreadState)
{
long Position = (long)ThreadState.X0;
int Handle = (int)ThreadState.X1;
KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
if (Thread == null)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
if (Process.GetThread(ThreadState.Tpidr) == Thread)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread);
return;
}
Memory.WriteUInt64(Position + 0x0, ThreadState.X0);
Memory.WriteUInt64(Position + 0x8, ThreadState.X1);
Memory.WriteUInt64(Position + 0x10, ThreadState.X2);
Memory.WriteUInt64(Position + 0x18, ThreadState.X3);
Memory.WriteUInt64(Position + 0x20, ThreadState.X4);
Memory.WriteUInt64(Position + 0x28, ThreadState.X5);
Memory.WriteUInt64(Position + 0x30, ThreadState.X6);
Memory.WriteUInt64(Position + 0x38, ThreadState.X7);
Memory.WriteUInt64(Position + 0x40, ThreadState.X8);
Memory.WriteUInt64(Position + 0x48, ThreadState.X9);
Memory.WriteUInt64(Position + 0x50, ThreadState.X10);
Memory.WriteUInt64(Position + 0x58, ThreadState.X11);
Memory.WriteUInt64(Position + 0x60, ThreadState.X12);
Memory.WriteUInt64(Position + 0x68, ThreadState.X13);
Memory.WriteUInt64(Position + 0x70, ThreadState.X14);
Memory.WriteUInt64(Position + 0x78, ThreadState.X15);
Memory.WriteUInt64(Position + 0x80, ThreadState.X16);
Memory.WriteUInt64(Position + 0x88, ThreadState.X17);
Memory.WriteUInt64(Position + 0x90, ThreadState.X18);
Memory.WriteUInt64(Position + 0x98, ThreadState.X19);
Memory.WriteUInt64(Position + 0xa0, ThreadState.X20);
Memory.WriteUInt64(Position + 0xa8, ThreadState.X21);
Memory.WriteUInt64(Position + 0xb0, ThreadState.X22);
Memory.WriteUInt64(Position + 0xb8, ThreadState.X23);
Memory.WriteUInt64(Position + 0xc0, ThreadState.X24);
Memory.WriteUInt64(Position + 0xc8, ThreadState.X25);
Memory.WriteUInt64(Position + 0xd0, ThreadState.X26);
Memory.WriteUInt64(Position + 0xd8, ThreadState.X27);
Memory.WriteUInt64(Position + 0xe0, ThreadState.X28);
Memory.WriteUInt64(Position + 0xe8, ThreadState.X29);
Memory.WriteUInt64(Position + 0xf0, ThreadState.X30);
Memory.WriteUInt64(Position + 0xf8, ThreadState.X31);
Memory.WriteInt64(Position + 0x100, Thread.LastPc);
Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr);
Memory.WriteVector128(Position + 0x110, ThreadState.V0);
Memory.WriteVector128(Position + 0x120, ThreadState.V1);
Memory.WriteVector128(Position + 0x130, ThreadState.V2);
Memory.WriteVector128(Position + 0x140, ThreadState.V3);
Memory.WriteVector128(Position + 0x150, ThreadState.V4);
Memory.WriteVector128(Position + 0x160, ThreadState.V5);
Memory.WriteVector128(Position + 0x170, ThreadState.V6);
Memory.WriteVector128(Position + 0x180, ThreadState.V7);
Memory.WriteVector128(Position + 0x190, ThreadState.V8);
Memory.WriteVector128(Position + 0x1a0, ThreadState.V9);
Memory.WriteVector128(Position + 0x1b0, ThreadState.V10);
Memory.WriteVector128(Position + 0x1c0, ThreadState.V11);
Memory.WriteVector128(Position + 0x1d0, ThreadState.V12);
Memory.WriteVector128(Position + 0x1e0, ThreadState.V13);
Memory.WriteVector128(Position + 0x1f0, ThreadState.V14);
Memory.WriteVector128(Position + 0x200, ThreadState.V15);
Memory.WriteVector128(Position + 0x210, ThreadState.V16);
Memory.WriteVector128(Position + 0x220, ThreadState.V17);
Memory.WriteVector128(Position + 0x230, ThreadState.V18);
Memory.WriteVector128(Position + 0x240, ThreadState.V19);
Memory.WriteVector128(Position + 0x250, ThreadState.V20);
Memory.WriteVector128(Position + 0x260, ThreadState.V21);
Memory.WriteVector128(Position + 0x270, ThreadState.V22);
Memory.WriteVector128(Position + 0x280, ThreadState.V23);
Memory.WriteVector128(Position + 0x290, ThreadState.V24);
Memory.WriteVector128(Position + 0x2a0, ThreadState.V25);
Memory.WriteVector128(Position + 0x2b0, ThreadState.V26);
Memory.WriteVector128(Position + 0x2c0, ThreadState.V27);
Memory.WriteVector128(Position + 0x2d0, ThreadState.V28);
Memory.WriteVector128(Position + 0x2e0, ThreadState.V29);
Memory.WriteVector128(Position + 0x2f0, ThreadState.V30);
Memory.WriteVector128(Position + 0x300, ThreadState.V31);
Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr);
Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr);
Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr);
ThreadState.X0 = 0;
}
}
}

View file

@ -0,0 +1,523 @@
using ChocolArm64.State;
using Ryujinx.HLE.Logging;
using System;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.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;
Device.Log.PrintDebug(LogClass.KernelSvc,
"OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " +
"MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
"WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8"));
if (IsPointingInsideKernel(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (IsAddressNotWordAligned(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
if (OwnerThread == null)
{
Device.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)
{
Device.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;
Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16"));
if (IsPointingInsideKernel(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (IsAddressNotWordAligned(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
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;
Device.Log.PrintDebug(LogClass.KernelSvc,
"MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
"CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
"ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " +
"Timeout = 0x" + Timeout .ToString("x16"));
if (IsPointingInsideKernel(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (IsAddressNotWordAligned(MutexAddress))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
if (Thread == null)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
return;
}
KThread WaitThread = Process.GetThread(ThreadState.Tpidr);
if (!CondVarWait(WaitThread, 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;
Device.Log.PrintDebug(LogClass.KernelSvc,
"CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
"Count = 0x" + Count .ToString("x8"));
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count);
ThreadState.X0 = 0;
}
private void MutexLock(
KThread CurrThread,
KThread WaitThread,
int OwnerThreadHandle,
int WaitThreadHandle,
long MutexAddress)
{
lock (Process.ThreadSyncLock)
{
int MutexValue = Memory.ReadInt32(MutexAddress);
Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
{
return;
}
CurrThread.WaitHandle = WaitThreadHandle;
CurrThread.MutexAddress = MutexAddress;
InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread);
}
Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
Process.Scheduler.EnterWait(CurrThread);
}
private void SvcWaitForAddress(AThreadState ThreadState)
{
long Address = (long)ThreadState.X0;
ArbitrationType Type = (ArbitrationType)ThreadState.X1;
int Value = (int)ThreadState.X2;
ulong Timeout = ThreadState.X3;
Device.Log.PrintDebug(LogClass.KernelSvc,
"Address = 0x" + Address.ToString("x16") + ", " +
"ArbitrationType = 0x" + Type .ToString() + ", " +
"Value = 0x" + Value .ToString("x8") + ", " +
"Timeout = 0x" + Timeout.ToString("x16"));
if (IsPointingInsideKernel(Address))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
return;
}
if (IsAddressNotWordAligned(Address))
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
return;
}
switch (Type)
{
case ArbitrationType.WaitIfLessThan:
ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
break;
case ArbitrationType.DecrementAndWaitIfLessThan:
ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
break;
case ArbitrationType.WaitIfEqual:
ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
break;
default:
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
break;
}
}
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, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress);
if (OwnerThread == CurrThread)
{
throw new InvalidOperationException();
}
if (OwnerThread != null)
{
//Remove all waiting mutex from the old owner,
//and insert then on the new owner.
UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress);
CurrThread.UpdatePriority();
int HasListeners = Count >= 2 ? MutexHasListenersMask : 0;
Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle);
OwnerThread.WaitHandle = 0;
OwnerThread.MutexAddress = 0;
OwnerThread.CondVarAddress = 0;
OwnerThread.MutexOwner = null;
OwnerThread.UpdatePriority();
Process.Scheduler.WakeUp(OwnerThread);
Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
}
else
{
Memory.WriteInt32ToSharedAddr(MutexAddress, 0);
Device.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)
{
MutexUnlock(WaitThread, MutexAddress);
WaitThread.CondVarSignaled = false;
Process.ThreadArbiterList.Add(WaitThread);
}
Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
if (Timeout != ulong.MaxValue)
{
Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
lock (Process.ThreadSyncLock)
{
if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
{
if (WaitThread.MutexOwner != null)
{
WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread);
WaitThread.MutexOwner.UpdatePriority();
WaitThread.MutexOwner = null;
}
Process.ThreadArbiterList.Remove(WaitThread);
Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
return false;
}
}
}
else
{
Process.Scheduler.EnterWait(WaitThread);
}
return true;
}
private void CondVarSignal(
AThreadState ThreadState,
KThread CurrThread,
long CondVarAddress,
int Count)
{
lock (Process.ThreadSyncLock)
{
while (Count == -1 || Count-- > 0)
{
KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress);
if (WaitThread == null)
{
Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
break;
}
WaitThread.CondVarSignaled = true;
long MutexAddress = WaitThread.MutexAddress;
Memory.SetExclusive(ThreadState, MutexAddress);
int MutexValue = Memory.ReadInt32(MutexAddress);
while (MutexValue != 0)
{
if (Memory.TestExclusive(ThreadState, MutexAddress))
{
//Wait until the lock is released.
InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread);
Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask);
Memory.ClearExclusiveForStore(ThreadState);
break;
}
Memory.SetExclusive(ThreadState, MutexAddress);
MutexValue = Memory.ReadInt32(MutexAddress);
}
Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
if (MutexValue == 0)
{
//Give the lock to this thread.
Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle);
WaitThread.WaitHandle = 0;
WaitThread.MutexAddress = 0;
WaitThread.CondVarAddress = 0;
WaitThread.MutexOwner?.UpdatePriority();
WaitThread.MutexOwner = null;
Process.Scheduler.WakeUp(WaitThread);
}
}
}
}
private void UpdateMutexOwnerUnsafe(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.
for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
{
KThread Thread = CurrThread.MutexWaiters[Index];
if (Thread.MutexAddress == MutexAddress)
{
CurrThread.MutexWaiters.RemoveAt(Index--);
InsertWaitingMutexThreadUnsafe(NewOwner, Thread);
}
}
}
private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread)
{
KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
if (OwnerThread == null)
{
Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
return;
}
InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread);
}
private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread)
{
WaitThread.MutexOwner = OwnerThread;
if (!OwnerThread.MutexWaiters.Contains(WaitThread))
{
OwnerThread.MutexWaiters.Add(WaitThread);
OwnerThread.UpdatePriority();
}
}
private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress)
{
int Count = 0;
KThread WakeThread = null;
foreach (KThread Thread in OwnerThread.MutexWaiters)
{
if (Thread.MutexAddress != MutexAddress)
{
continue;
}
if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
{
WakeThread = Thread;
}
Count++;
}
if (WakeThread != null)
{
OwnerThread.MutexWaiters.Remove(WakeThread);
}
return (WakeThread, Count);
}
private KThread PopCondVarThreadUnsafe(long CondVarAddress)
{
KThread WakeThread = null;
foreach (KThread Thread in Process.ThreadArbiterList)
{
if (Thread.CondVarAddress != CondVarAddress)
{
continue;
}
if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
{
WakeThread = Thread;
}
}
if (WakeThread != null)
{
Process.ThreadArbiterList.Remove(WakeThread);
}
return WakeThread;
}
private bool IsPointingInsideKernel(long Address)
{
return ((ulong)Address + 0x1000000000) < 0xffffff000;
}
private bool IsAddressNotWordAligned(long Address)
{
return (Address & 3) != 0;
}
}
}

View file

@ -0,0 +1,158 @@
namespace Ryujinx.HLE.HOS.Kernel
{
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;
}
}
}
}

438
Ryujinx.HLE/HOS/Process.cs Normal file
View file

@ -0,0 +1,438 @@
using ChocolArm64;
using ChocolArm64.Events;
using ChocolArm64.Memory;
using ChocolArm64.State;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Diagnostics;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Services.Nv;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.HOS
{
class Process : IDisposable
{
private const int TickFreq = 19_200_000;
public Switch Device { get; private set; }
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 KMemoryManager MemoryManager { get; private set; }
private List<KTlsPageManager> TlsPages;
public KProcessScheduler Scheduler { get; private set; }
public List<KThread> ThreadArbiterList { get; private set; }
public object ThreadSyncLock { get; private set; }
public Npdm MetaData { get; private set; }
public KProcessHandleTable HandleTable { get; private set; }
public AppletStateMgr AppletState { get; private set; }
private SvcHandler SvcHandler;
private ConcurrentDictionary<long, KThread> Threads;
private List<Executable> Executables;
private Dictionary<long, string> SymbolTable;
private long ImageBase;
private bool ShouldDispose;
private bool Disposed;
public Process(Switch Device, KProcessScheduler Scheduler, int ProcessId, Npdm MetaData)
{
this.Device = Device;
this.Scheduler = Scheduler;
this.MetaData = MetaData;
this.ProcessId = ProcessId;
Memory = new AMemory(Device.Memory.RamPointer);
MemoryManager = new KMemoryManager(this);
TlsPages = new List<KTlsPageManager>();
ThreadArbiterList = new List<KThread>();
ThreadSyncLock = new object();
HandleTable = new KProcessHandleTable();
AppletState = new AppletStateMgr();
SvcHandler = new SvcHandler(Device, this);
Threads = new ConcurrentDictionary<long, KThread>();
Executables = new List<Executable>();
ImageBase = MemoryManager.CodeRegionStart;
}
public void LoadProgram(IExecutable Program)
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
Device.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}.");
Executable Executable = new Executable(Program, MemoryManager, Memory, ImageBase);
Executables.Add(Executable);
ImageBase = IntUtils.AlignUp(Executable.ImageEnd, KMemoryManager.PageSize);
}
public void SetEmptyArgs()
{
//TODO: This should be part of Run.
ImageBase += KMemoryManager.PageSize;
}
public bool Run(bool NeedsHbAbi = false)
{
if (Disposed)
{
throw new ObjectDisposedException(nameof(Process));
}
this.NeedsHbAbi = NeedsHbAbi;
if (Executables.Count == 0)
{
return false;
}
MakeSymbolTable();
long MainStackTop = MemoryManager.CodeRegionEnd - KMemoryManager.PageSize;
long MainStackSize = 1 * 1024 * 1024;
long MainStackBottom = MainStackTop - MainStackSize;
MemoryManager.HleMapCustom(
MainStackBottom,
MainStackSize,
MemoryState.MappedMemory,
MemoryPermission.ReadAndWrite);
int Handle = MakeThread(Executables[0].ImageBase, MainStackTop, 0, 44, 0);
if (Handle == -1)
{
return false;
}
KThread MainThread = HandleTable.GetData<KThread>(Handle);
if (NeedsHbAbi)
{
HbAbiDataPosition = IntUtils.AlignUp(Executables[0].ImageEnd, KMemoryManager.PageSize);
const long HbAbiDataSize = KMemoryManager.PageSize;
MemoryManager.HleMapCustom(
HbAbiDataPosition,
HbAbiDataSize,
MemoryState.MappedMemory,
MemoryPermission.ReadAndWrite);
string SwitchPath = Device.FileSystem.SystemPathToSwitchPath(Executables[0].FilePath);
Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle, SwitchPath);
MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition;
MainThread.Thread.ThreadState.X1 = ulong.MaxValue;
}
Scheduler.StartThread(MainThread);
return true;
}
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);
long Tpidr = GetFreeTls();
int ThreadId = (int)((Tpidr - MemoryManager.TlsIoRegionStart) / 0x200) + 1;
KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority, ThreadId);
Thread.LastPc = EntryPoint;
int Handle = HandleTable.OpenHandle(Thread);
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 long GetFreeTls()
{
long Position;
lock (TlsPages)
{
for (int Index = 0; Index < TlsPages.Count; Index++)
{
if (TlsPages[Index].TryGetFreeTlsAddr(out Position))
{
return Position;
}
}
long PagePosition = MemoryManager.HleMapTlsPage();
KTlsPageManager TlsPage = new KTlsPageManager(PagePosition);
TlsPages.Add(TlsPage);
TlsPage.TryGetFreeTlsAddr(out Position);
}
return Position;
}
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;
}
}
Device.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) + ")");
}
Device.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 void ThreadFinished(object sender, EventArgs e)
{
if (sender is AThread Thread)
{
Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread);
Scheduler.RemoveThread(KernelThread);
KernelThread.WaitEvent.Set();
}
if (Threads.Count == 0)
{
Device.System.ExitProcess(ProcessId);
}
}
public KThread GetThread(long Tpidr)
{
if (!Threads.TryGetValue(Tpidr, out KThread Thread))
{
throw new InvalidOperationException();
}
return Thread;
}
private void Unload()
{
if (Disposed || Threads.Count > 0)
{
return;
}
Disposed = true;
foreach (object Obj in HandleTable.Clear())
{
if (Obj is KSession Session)
{
Session.Dispose();
}
}
INvDrvServices.UnloadProcess(this);
AppletState.Dispose();
if (NeedsHbAbi && Executables.Count > 0 && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix))
{
File.Delete(Executables[0].FilePath);
}
Device.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting...");
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
if (Threads.Count > 0)
{
foreach (KThread Thread in Threads.Values)
{
Thread.Thread.StopExecution();
Scheduler.ForceWakeUp(Thread);
}
}
else
{
Unload();
}
}
}
}
}

View file

@ -0,0 +1,39 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using System.IO;
namespace Ryujinx.HLE.HOS
{
class ServiceCtx
{
public Switch Device { 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 Device,
Process Process,
AMemory Memory,
KSession Session,
IpcMessage Request,
IpcMessage Response,
BinaryReader RequestData,
BinaryWriter ResponseData)
{
this.Device = Device;
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,7 @@
namespace Ryujinx.HLE.HOS.Services.Acc
{
static class AccErr
{
public const int UserNotFound = 100;
}
}

View file

@ -0,0 +1,125 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.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(Context.Device.System.State.GetUserCount());
return 0;
}
public long GetUserExistence(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
Context.ResponseData.Write(Context.Device.System.State.TryGetUser(Uuid, out _) ? 1 : 0);
return 0;
}
public long ListAllUsers(ServiceCtx Context)
{
return WriteUserList(Context, Context.Device.System.State.GetAllUsers());
}
public long ListOpenUsers(ServiceCtx Context)
{
return WriteUserList(Context, Context.Device.System.State.GetOpenUsers());
}
private long WriteUserList(ServiceCtx Context, IEnumerable<UserProfile> Profiles)
{
long OutputPosition = Context.Request.RecvListBuff[0].Position;
long OutputSize = Context.Request.RecvListBuff[0].Size;
long Offset = 0;
foreach (UserProfile Profile in Profiles)
{
if ((ulong)Offset + 16 > (ulong)OutputSize)
{
break;
}
byte[] Uuid = Profile.Uuid.Bytes;
for (int Index = Uuid.Length - 1; Index >= 0; Index--)
{
Context.Memory.WriteByte(OutputPosition + Offset++, Uuid[Index]);
}
}
return 0;
}
public long GetLastOpenedUser(ServiceCtx Context)
{
UserProfile LastOpened = Context.Device.System.State.LastOpenUser;
LastOpened.Uuid.Write(Context.ResponseData);
return 0;
}
public long GetProfile(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
if (!Context.Device.System.State.TryGetUser(Uuid, out UserProfile Profile))
{
Context.Device.Log.PrintWarning(LogClass.ServiceAcc, $"User 0x{Uuid} not found!");
return MakeError(ErrorModule.Account, AccErr.UserNotFound);
}
MakeObject(Context, new IProfile(Profile));
return 0;
}
public long InitializeApplicationInfo(ServiceCtx Context)
{
Context.Device.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.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
}
public long GetAccountId(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
Context.ResponseData.Write(0xcafeL);
return 0;
}
}
}

View file

@ -0,0 +1,58 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Acc
{
class IProfile : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private UserProfile Profile;
public IProfile(UserProfile Profile)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Get },
{ 1, GetBase }
};
this.Profile = Profile;
}
public long Get(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
long Position = Context.Request.ReceiveBuff[0].Position;
AMemoryHelper.FillWithZeros(Context.Memory, Position, 0x80);
Context.Memory.WriteInt32(Position, 0);
Context.Memory.WriteInt32(Position + 4, 1);
Context.Memory.WriteInt64(Position + 8, 1);
return GetBase(Context);
}
public long GetBase(ServiceCtx Context)
{
Profile.Uuid.Write(Context.ResponseData);
Context.ResponseData.Write(Profile.LastModifiedTimestamp);
byte[] Username = StringUtils.GetFixedLengthBytes(Profile.Name, 0x20, Encoding.UTF8);
Context.ResponseData.Write(Username);
return 0;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am
{
static class AmErr
{
public const int NoMessages = 3;
}
}

View file

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

View file

@ -0,0 +1,27 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
return 0;
}
public long GetDesiredLanguage(ServiceCtx Context)
{
Context.ResponseData.Write(Context.Device.System.State.DesiredLanguageCode);
return 0;
}
public long SetTerminateResult(ServiceCtx Context)
{
int ErrorCode = Context.RequestData.ReadInt32();
string Result = GetFormattedErrorCode(ErrorCode);
Context.Device.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
Context.ResponseData.Write(0L);
return 0;
}
public long InitializeGamePlayRecording(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetGamePlayRecordingState(ServiceCtx Context)
{
int State = Context.RequestData.ReadInt32();
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,83 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetMainAppletExpectedMasterVolume(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long ChangeMainAppletMasterVolume(ServiceCtx Context)
{
float Unknown0 = Context.RequestData.ReadSingle();
long Unknown1 = Context.RequestData.ReadInt64();
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetTransparentVolumeRate(ServiceCtx Context)
{
float Unknown0 = Context.RequestData.ReadSingle();
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,115 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.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)
{
OperationMode Mode = Context.Device.System.State.DockedMode
? OperationMode.Docked
: OperationMode.Handheld;
Context.ResponseData.Write((byte)Mode);
return 0;
}
public long GetPerformanceMode(ServiceCtx Context)
{
Apm.PerformanceMode Mode = Context.Device.System.State.DockedMode
? Apm.PerformanceMode.Docked
: Apm.PerformanceMode.Handheld;
Context.ResponseData.Write((int)Mode);
return 0;
}
public long GetBootMode(ServiceCtx Context)
{
Context.ResponseData.Write((byte)0); //Unknown value.
Context.Device.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);
return 0;
}
public long GetDefaultDisplayResolutionChangeEvent(ServiceCtx Context)
{
int Handle = Context.Process.HandleTable.OpenHandle(DisplayResolutionChangeEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,71 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long Start(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetResult(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long PushInData(ServiceCtx Context)
{
Context.Device.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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,145 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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>()
{
{ 0, Exit },
{ 1, LockExit },
{ 2, UnlockExit },
{ 9, GetLibraryAppletLaunchableEvent },
{ 10, SetScreenShotPermission },
{ 11, SetOperationModeChangedNotification },
{ 12, SetPerformanceModeChangedNotification },
{ 13, SetFocusHandlingMode },
{ 14, SetRestartMessageEnabled },
{ 16, SetOutOfFocusSuspendingEnabled },
{ 19, SetScreenShotImageOrientation },
{ 50, SetHandlesRequestToDisplay }
};
LaunchableEvent = new KEvent();
}
public long Exit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long LockExit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long UnlockExit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetLibraryAppletLaunchableEvent(ServiceCtx Context)
{
LaunchableEvent.WaitEvent.Set();
int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetScreenShotPermission(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOperationModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetPerformanceModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetRestartMessageEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetScreenShotImageOrientation(ServiceCtx Context)
{
int Orientation = Context.RequestData.ReadInt32();
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetHandlesRequestToDisplay(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,31 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
Context.ResponseData.Write(0L);
return 0;
}
public long AcquireForegroundRights(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,27 @@
using System.IO;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.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.Device.Log.PrintStub(LogClass.ServiceApm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,18 @@
namespace Ryujinx.HLE.HOS.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.HOS.Services.Apm
{
enum PerformanceMode
{
Handheld = 0,
Docked = 1
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Aud
{
static class AudErr
{
public const int DeviceNotFound = 1;
public const int UnsupportedRevision = 2;
public const int UnsupportedSampleRate = 3;
}
}

View file

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut
{
[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,163 @@
using ChocolArm64.Memory;
using Ryujinx.Audio;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut
{
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, AppendAudioOutBufferAuto },
{ 8, GetReleasedAudioOutBufferAuto }
};
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)
{
return AppendAudioOutBufferImpl(Context, Context.Request.SendBuff[0].Position);
}
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;
return GetReleasedAudioOutBufferImpl(Context, Position, Size);
}
public long ContainsAudioOutBuffer(ServiceCtx Context)
{
long Tag = Context.RequestData.ReadInt64();
Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0);
return 0;
}
public long AppendAudioOutBufferAuto(ServiceCtx Context)
{
(long Position, long Size) = Context.Request.GetBufferType0x21();
return AppendAudioOutBufferImpl(Context, Position);
}
public long AppendAudioOutBufferImpl(ServiceCtx Context, long Position)
{
long Tag = Context.RequestData.ReadInt64();
AudioOutData Data = AMemoryHelper.Read<AudioOutData>(
Context.Memory,
Position);
byte[] Buffer = Context.Memory.ReadBytes(
Data.SampleBufferPtr,
Data.SampleBufferSize);
AudioOut.AppendBuffer(Track, Tag, Buffer);
return 0;
}
public long GetReleasedAudioOutBufferAuto(ServiceCtx Context)
{
(long Position, long Size) = Context.Request.GetBufferType0x22();
return GetReleasedAudioOutBufferImpl(Context, Position, Size);
}
public long GetReleasedAudioOutBufferImpl(ServiceCtx Context, long Position, long 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 void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
AudioOut.CloseTrack(Track);
ReleaseEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
static class AudioConsts
{
public const int HostSampleRate = 48000;
public const int HostChannelsCount = 2;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct BehaviorIn
{
public long Unknown0;
public long Unknown8;
}
}

View file

@ -0,0 +1,16 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)]
struct BiquadFilter
{
public byte Enable;
public byte Padding;
public short B0;
public short B1;
public short B2;
public short A1;
public short A2;
}
}

View file

@ -0,0 +1,318 @@
using ChocolArm64.Memory;
using Ryujinx.Audio;
using Ryujinx.Audio.Adpcm;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
class IAudioRenderer : IpcService, IDisposable
{
//This is the amount of samples that are going to be appended
//each time that RequestUpdateAudioRenderer is called. Ideally,
//this value shouldn't be neither too small (to avoid the player
//starving due to running out of samples) or too large (to avoid
//high latency).
private const int MixBufferSamplesCount = 960;
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent UpdateEvent;
private AMemory Memory;
private IAalOutput AudioOut;
private AudioRendererParameter Params;
private MemoryPoolContext[] MemoryPools;
private VoiceContext[] Voices;
private int Track;
public IAudioRenderer(AMemory Memory, IAalOutput AudioOut, AudioRendererParameter Params)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, RequestUpdateAudioRenderer },
{ 5, StartAudioRenderer },
{ 6, StopAudioRenderer },
{ 7, QuerySystemEvent }
};
UpdateEvent = new KEvent();
this.Memory = Memory;
this.AudioOut = AudioOut;
this.Params = Params;
Track = AudioOut.OpenTrack(
AudioConsts.HostSampleRate,
AudioConsts.HostChannelsCount,
AudioCallback);
MemoryPools = CreateArray<MemoryPoolContext>(Params.EffectCount + Params.VoiceCount * 4);
Voices = CreateArray<VoiceContext>(Params.VoiceCount);
InitializeAudioOut();
}
private void AudioCallback()
{
UpdateEvent.WaitEvent.Set();
}
private static T[] CreateArray<T>(int Size) where T : new()
{
T[] Output = new T[Size];
for (int Index = 0; Index < Size; Index++)
{
Output[Index] = new T();
}
return Output;
}
private void InitializeAudioOut()
{
AppendMixedBuffer(0);
AppendMixedBuffer(1);
AppendMixedBuffer(2);
AudioOut.Start(Track);
}
public long RequestUpdateAudioRenderer(ServiceCtx Context)
{
long OutputPosition = Context.Request.ReceiveBuff[0].Position;
long OutputSize = Context.Request.ReceiveBuff[0].Size;
AMemoryHelper.FillWithZeros(Context.Memory, OutputPosition, (int)OutputSize);
long InputPosition = Context.Request.SendBuff[0].Position;
StructReader Reader = new StructReader(Context.Memory, InputPosition);
StructWriter Writer = new StructWriter(Context.Memory, OutputPosition);
UpdateDataHeader InputHeader = Reader.Read<UpdateDataHeader>();
Reader.Read<BehaviorIn>(InputHeader.BehaviorSize);
MemoryPoolIn[] MemoryPoolsIn = Reader.Read<MemoryPoolIn>(InputHeader.MemoryPoolSize);
for (int Index = 0; Index < MemoryPoolsIn.Length; Index++)
{
MemoryPoolIn MemoryPool = MemoryPoolsIn[Index];
if (MemoryPool.State == MemoryPoolState.RequestAttach)
{
MemoryPools[Index].OutStatus.State = MemoryPoolState.Attached;
}
else if (MemoryPool.State == MemoryPoolState.RequestDetach)
{
MemoryPools[Index].OutStatus.State = MemoryPoolState.Detached;
}
}
Reader.Read<VoiceChannelResourceIn>(InputHeader.VoiceResourceSize);
VoiceIn[] VoicesIn = Reader.Read<VoiceIn>(InputHeader.VoiceSize);
for (int Index = 0; Index < VoicesIn.Length; Index++)
{
VoiceIn Voice = VoicesIn[Index];
VoiceContext VoiceCtx = Voices[Index];
VoiceCtx.SetAcquireState(Voice.Acquired != 0);
if (Voice.Acquired == 0)
{
continue;
}
if (Voice.FirstUpdate != 0)
{
VoiceCtx.AdpcmCtx = GetAdpcmDecoderContext(
Voice.AdpcmCoeffsPosition,
Voice.AdpcmCoeffsSize);
VoiceCtx.SampleFormat = Voice.SampleFormat;
VoiceCtx.SampleRate = Voice.SampleRate;
VoiceCtx.ChannelsCount = Voice.ChannelsCount;
VoiceCtx.SetBufferIndex(Voice.BaseWaveBufferIndex);
}
VoiceCtx.WaveBuffers[0] = Voice.WaveBuffer0;
VoiceCtx.WaveBuffers[1] = Voice.WaveBuffer1;
VoiceCtx.WaveBuffers[2] = Voice.WaveBuffer2;
VoiceCtx.WaveBuffers[3] = Voice.WaveBuffer3;
VoiceCtx.Volume = Voice.Volume;
VoiceCtx.PlayState = Voice.PlayState;
}
UpdateAudio();
UpdateDataHeader OutputHeader = new UpdateDataHeader();
int UpdateHeaderSize = Marshal.SizeOf<UpdateDataHeader>();
OutputHeader.Revision = IAudioRendererManager.RevMagic;
OutputHeader.BehaviorSize = 0xb0;
OutputHeader.MemoryPoolSize = (Params.EffectCount + Params.VoiceCount * 4) * 0x10;
OutputHeader.VoiceSize = Params.VoiceCount * 0x10;
OutputHeader.EffectSize = Params.EffectCount * 0x10;
OutputHeader.SinkSize = Params.SinkCount * 0x20;
OutputHeader.PerformanceManagerSize = 0x10;
OutputHeader.TotalSize = UpdateHeaderSize +
OutputHeader.BehaviorSize +
OutputHeader.MemoryPoolSize +
OutputHeader.VoiceSize +
OutputHeader.EffectSize +
OutputHeader.SinkSize +
OutputHeader.PerformanceManagerSize;
Writer.Write(OutputHeader);
foreach (MemoryPoolContext MemoryPool in MemoryPools)
{
Writer.Write(MemoryPool.OutStatus);
}
foreach (VoiceContext Voice in Voices)
{
Writer.Write(Voice.OutStatus);
}
return 0;
}
public long StartAudioRenderer(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long StopAudioRenderer(ServiceCtx Context)
{
Context.Device.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;
}
private AdpcmDecoderContext GetAdpcmDecoderContext(long Position, long Size)
{
if (Size == 0)
{
return null;
}
AdpcmDecoderContext Context = new AdpcmDecoderContext();
Context.Coefficients = new short[Size >> 1];
for (int Offset = 0; Offset < Size; Offset += 2)
{
Context.Coefficients[Offset >> 1] = Memory.ReadInt16(Position + Offset);
}
return Context;
}
private void UpdateAudio()
{
long[] Released = AudioOut.GetReleasedBuffers(Track, 2);
for (int Index = 0; Index < Released.Length; Index++)
{
AppendMixedBuffer(Released[Index]);
}
}
private void AppendMixedBuffer(long Tag)
{
int[] MixBuffer = new int[MixBufferSamplesCount * AudioConsts.HostChannelsCount];
foreach (VoiceContext Voice in Voices)
{
if (!Voice.Playing)
{
continue;
}
int OutOffset = 0;
int PendingSamples = MixBufferSamplesCount;
while (PendingSamples > 0)
{
int[] Samples = Voice.GetBufferData(Memory, PendingSamples, out int ReturnedSamples);
if (ReturnedSamples == 0)
{
break;
}
PendingSamples -= ReturnedSamples;
for (int Offset = 0; Offset < Samples.Length; Offset++)
{
int Sample = (int)(Samples[Offset] * Voice.Volume);
MixBuffer[OutOffset++] += Sample;
}
}
}
AudioOut.AppendBuffer(Track, Tag, GetFinalBuffer(MixBuffer));
}
private static short[] GetFinalBuffer(int[] Buffer)
{
short[] Output = new short[Buffer.Length];
for (int Offset = 0; Offset < Buffer.Length; Offset++)
{
Output[Offset] = DspUtils.Saturate(Buffer[Offset]);
}
return Output;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
AudioOut.CloseTrack(Track);
UpdateEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
class MemoryPoolContext
{
public MemoryPoolOut OutStatus;
public MemoryPoolContext()
{
OutStatus.State = MemoryPoolState.Detached;
}
}
}

View file

@ -0,0 +1,14 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)]
struct MemoryPoolIn
{
public long Address;
public long Size;
public MemoryPoolState State;
public int Unknown14;
public long Unknown18;
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct MemoryPoolOut
{
public MemoryPoolState State;
public int Unknown14;
public long Unknown18;
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
enum MemoryPoolState : int
{
Invalid = 0,
Unknown = 1,
RequestDetach = 2,
Detached = 3,
RequestAttach = 4,
Attached = 5,
Released = 6
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
enum PlayState : byte
{
Playing = 0,
Stopped = 1,
Paused = 2
}
}

View file

@ -0,0 +1,191 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
static class Resampler
{
#region "LookUp Tables"
private static short[] CurveLut0 = new short[]
{
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
};
private static short[] CurveLut1 = new short[]
{
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
-568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
-990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
-1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
-1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
-1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
-1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
-2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
-2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
-2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
-2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
-2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
-1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
-1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
-1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
-1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
-1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
-1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
-1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
-907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
-783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
-668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
-563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
-468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
-382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
-240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
-180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
-128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
-80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
-36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
};
private static short[] CurveLut2 = new short[]
{
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
-71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
-163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
-284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
-317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
-336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
-341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
-337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
-325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
-306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
-283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
-258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
-230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
-202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
-174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
-121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
-98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
-76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
-58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
-42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
};
#endregion
public static int[] Resample2Ch(
int[] Buffer,
int SrcSampleRate,
int DstSampleRate,
int SamplesCount,
ref int FracPart)
{
if (Buffer == null)
{
throw new ArgumentNullException(nameof(Buffer));
}
if (SrcSampleRate <= 0)
{
throw new ArgumentOutOfRangeException(nameof(SrcSampleRate));
}
if (DstSampleRate <= 0)
{
throw new ArgumentOutOfRangeException(nameof(DstSampleRate));
}
double Ratio = (double)SrcSampleRate / DstSampleRate;
int NewSamplesCount = (int)(SamplesCount / Ratio);
int Step = (int)(Ratio * 0x8000);
int[] Output = new int[NewSamplesCount * 2];
short[] Lut;
if (Step > 0xaaaa)
{
Lut = CurveLut0;
}
else if (Step <= 0x8000)
{
Lut = CurveLut1;
}
else
{
Lut = CurveLut2;
}
int InOffs = 0;
for (int OutOffs = 0; OutOffs < Output.Length; OutOffs += 2)
{
int LutIndex = (FracPart >> 8) * 4;
int Sample0 = Buffer[(InOffs + 0) * 2 + 0] * Lut[LutIndex + 0] +
Buffer[(InOffs + 1) * 2 + 0] * Lut[LutIndex + 1] +
Buffer[(InOffs + 2) * 2 + 0] * Lut[LutIndex + 2] +
Buffer[(InOffs + 3) * 2 + 0] * Lut[LutIndex + 3];
int Sample1 = Buffer[(InOffs + 0) * 2 + 1] * Lut[LutIndex + 0] +
Buffer[(InOffs + 1) * 2 + 1] * Lut[LutIndex + 1] +
Buffer[(InOffs + 2) * 2 + 1] * Lut[LutIndex + 2] +
Buffer[(InOffs + 3) * 2 + 1] * Lut[LutIndex + 3];
int NewOffset = FracPart + Step;
InOffs += NewOffset >> 15;
FracPart = NewOffset & 0x7fff;
Output[OutOffs + 0] = Sample0 >> 15;
Output[OutOffs + 1] = Sample1 >> 15;
}
return Output;
}
}
}

View file

@ -0,0 +1,22 @@
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
struct UpdateDataHeader
{
public int Revision;
public int BehaviorSize;
public int MemoryPoolSize;
public int VoiceSize;
public int VoiceResourceSize;
public int EffectSize;
public int MixeSize;
public int SinkSize;
public int PerformanceManagerSize;
public int Unknown24;
public int Unknown28;
public int Unknown2C;
public int Unknown30;
public int Unknown34;
public int Unknown38;
public int TotalSize;
}
}

View file

@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
struct VoiceChannelResourceIn
{
//???
}
}

View file

@ -0,0 +1,188 @@
using ChocolArm64.Memory;
using Ryujinx.Audio.Adpcm;
using System;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
class VoiceContext
{
private bool Acquired;
private bool BufferReload;
private int ResamplerFracPart;
private int BufferIndex;
private int Offset;
public int SampleRate;
public int ChannelsCount;
public float Volume;
public PlayState PlayState;
public SampleFormat SampleFormat;
public AdpcmDecoderContext AdpcmCtx;
public WaveBuffer[] WaveBuffers;
public VoiceOut OutStatus;
private int[] Samples;
public bool Playing => Acquired && PlayState == PlayState.Playing;
public VoiceContext()
{
WaveBuffers = new WaveBuffer[4];
}
public void SetAcquireState(bool NewState)
{
if (Acquired && !NewState)
{
//Release.
Reset();
}
Acquired = NewState;
}
private void Reset()
{
BufferReload = true;
BufferIndex = 0;
Offset = 0;
OutStatus.PlayedSamplesCount = 0;
OutStatus.PlayedWaveBuffersCount = 0;
OutStatus.VoiceDropsCount = 0;
}
public int[] GetBufferData(AMemory Memory, int MaxSamples, out int SamplesCount)
{
if (!Playing)
{
SamplesCount = 0;
return null;
}
if (BufferReload)
{
BufferReload = false;
UpdateBuffer(Memory);
}
WaveBuffer Wb = WaveBuffers[BufferIndex];
int MaxSize = Samples.Length - Offset;
int Size = MaxSamples * AudioConsts.HostChannelsCount;
if (Size > MaxSize)
{
Size = MaxSize;
}
int[] Output = new int[Size];
Array.Copy(Samples, Offset, Output, 0, Size);
SamplesCount = Size / AudioConsts.HostChannelsCount;
OutStatus.PlayedSamplesCount += SamplesCount;
Offset += Size;
if (Offset == Samples.Length)
{
Offset = 0;
if (Wb.Looping == 0)
{
SetBufferIndex((BufferIndex + 1) & 3);
}
OutStatus.PlayedWaveBuffersCount++;
if (Wb.LastBuffer != 0)
{
PlayState = PlayState.Paused;
}
}
return Output;
}
private void UpdateBuffer(AMemory Memory)
{
//TODO: Implement conversion for formats other
//than interleaved stereo (2 channels).
//As of now, it assumes that HostChannelsCount == 2.
WaveBuffer Wb = WaveBuffers[BufferIndex];
if (SampleFormat == SampleFormat.PcmInt16)
{
int SamplesCount = (int)(Wb.Size / (sizeof(short) * ChannelsCount));
Samples = new int[SamplesCount * AudioConsts.HostChannelsCount];
if (ChannelsCount == 1)
{
for (int Index = 0; Index < SamplesCount; Index++)
{
short Sample = Memory.ReadInt16(Wb.Position + Index * 2);
Samples[Index * 2 + 0] = Sample;
Samples[Index * 2 + 1] = Sample;
}
}
else
{
for (int Index = 0; Index < SamplesCount * 2; Index++)
{
Samples[Index] = Memory.ReadInt16(Wb.Position + Index * 2);
}
}
}
else if (SampleFormat == SampleFormat.Adpcm)
{
byte[] Buffer = Memory.ReadBytes(Wb.Position, Wb.Size);
Samples = AdpcmDecoder.Decode(Buffer, AdpcmCtx);
}
else
{
throw new InvalidOperationException();
}
if (SampleRate != AudioConsts.HostSampleRate)
{
//TODO: We should keep the frames being discarded (see the 4 below)
//on a buffer and include it on the next samples buffer, to allow
//the resampler to do seamless interpolation between wave buffers.
int SamplesCount = Samples.Length / AudioConsts.HostChannelsCount;
SamplesCount = Math.Max(SamplesCount - 4, 0);
Samples = Resampler.Resample2Ch(
Samples,
SampleRate,
AudioConsts.HostSampleRate,
SamplesCount,
ref ResamplerFracPart);
}
}
public void SetBufferIndex(int Index)
{
BufferIndex = Index & 3;
BufferReload = true;
}
}
}

View file

@ -0,0 +1,49 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
struct VoiceIn
{
public int VoiceSlot;
public int NodeId;
public byte FirstUpdate;
public byte Acquired;
public PlayState PlayState;
public SampleFormat SampleFormat;
public int SampleRate;
public int Priority;
public int Unknown14;
public int ChannelsCount;
public float Pitch;
public float Volume;
public BiquadFilter BiquadFilter0;
public BiquadFilter BiquadFilter1;
public int AppendedWaveBuffersCount;
public int BaseWaveBufferIndex;
public int Unknown44;
public long AdpcmCoeffsPosition;
public long AdpcmCoeffsSize;
public int VoiceDestination;
public int Padding;
public WaveBuffer WaveBuffer0;
public WaveBuffer WaveBuffer1;
public WaveBuffer WaveBuffer2;
public WaveBuffer WaveBuffer3;
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct VoiceOut
{
public long PlayedSamplesCount;
public int PlayedWaveBuffersCount;
public int VoiceDropsCount; //?
}
}

View file

@ -0,0 +1,20 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
{
[StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
struct WaveBuffer
{
public long Position;
public long Size;
public int FirstSampleOffset;
public int LastSampleOffset;
public byte Looping;
public byte LastBuffer;
public short Unknown1A;
public int Unknown1C;
public long AdpcmLoopContextPosition;
public long AdpcmLoopContextSize;
public long Unknown30;
}
}

View file

@ -0,0 +1,22 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Aud
{
[StructLayout(LayoutKind.Sequential)]
struct AudioRendererParameter
{
public int SampleRate;
public int SampleCount;
public int Unknown8;
public int MixCount;
public int VoiceCount;
public int SinkCount;
public int EffectCount;
public int PerformanceManagerCount;
public int VoiceDropEnable;
public int SplitterCount;
public int SplitterDestinationDataCount;
public int Unknown2C;
public int Revision;
}
}

View file

@ -0,0 +1,223 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
using System.Text;
namespace Ryujinx.HLE.HOS.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.Device.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.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveAudioDeviceName(ServiceCtx Context)
{
string Name = Context.Device.System.State.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.Device.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.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveChannelCount(ServiceCtx Context)
{
Context.ResponseData.Write(2);
Context.Device.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.Device.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.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetAudioDeviceOutputVolumeAuto(ServiceCtx Context)
{
Context.ResponseData.Write(1f);
Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
public long GetActiveAudioDeviceNameAuto(ServiceCtx Context)
{
string Name = Context.Device.System.State.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.Device.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.Device.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.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,170 @@
using ChocolArm64.Memory;
using Ryujinx.Audio;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Services.Aud.AudioOut;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
using System.Text;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Services.Aud
{
class IAudioOutManager : IpcService
{
private const string DefaultAudioOutput = "DeviceOut";
private const int DefaultSampleRate = 48000;
private const int DefaultChannelsCount = 2;
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 },
{ 2, ListAudioOutsAuto },
{ 3, OpenAudioOutAuto }
};
}
public long ListAudioOuts(ServiceCtx Context)
{
return ListAudioOutsImpl(
Context,
Context.Request.ReceiveBuff[0].Position,
Context.Request.ReceiveBuff[0].Size);
}
public long OpenAudioOut(ServiceCtx Context)
{
return OpenAudioOutImpl(
Context,
Context.Request.SendBuff[0].Position,
Context.Request.SendBuff[0].Size,
Context.Request.ReceiveBuff[0].Position,
Context.Request.ReceiveBuff[0].Size);
}
public long ListAudioOutsAuto(ServiceCtx Context)
{
(long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22();
return ListAudioOutsImpl(Context, RecvPosition, RecvSize);
}
public long OpenAudioOutAuto(ServiceCtx Context)
{
(long SendPosition, long SendSize) = Context.Request.GetBufferType0x21();
(long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22();
return OpenAudioOutImpl(
Context,
SendPosition,
SendSize,
RecvPosition,
RecvSize);
}
private long ListAudioOutsImpl(ServiceCtx Context, long Position, long 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.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
}
Context.ResponseData.Write(NameCount);
return 0;
}
private long OpenAudioOutImpl(ServiceCtx Context, long SendPosition, long SendSize, long ReceivePosition, long ReceiveSize)
{
string DeviceName = AMemoryHelper.ReadAsciiString(
Context.Memory,
SendPosition,
SendSize);
if (DeviceName == string.Empty)
{
DeviceName = DefaultAudioOutput;
}
if (DeviceName != DefaultAudioOutput)
{
Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid device name!");
return MakeError(ErrorModule.Audio, AudErr.DeviceNotFound);
}
byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName + "\0");
if ((ulong)DeviceNameBuffer.Length <= (ulong)ReceiveSize)
{
Context.Memory.WriteBytes(ReceivePosition, DeviceNameBuffer);
}
else
{
Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {ReceiveSize} too small!");
}
int SampleRate = Context.RequestData.ReadInt32();
int Channels = Context.RequestData.ReadInt32();
if (SampleRate == 0)
{
SampleRate = DefaultSampleRate;
}
if (SampleRate != DefaultSampleRate)
{
Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid sample rate!");
return MakeError(ErrorModule.Audio, AudErr.UnsupportedSampleRate);
}
Channels = (ushort)Channels;
if (Channels == 0)
{
Channels = DefaultChannelsCount;
}
KEvent ReleaseEvent = new KEvent();
ReleaseCallback Callback = () =>
{
ReleaseEvent.WaitEvent.Set();
};
IAalOutput AudioOut = Context.Device.AudioOut;
int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback);
MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track));
Context.ResponseData.Write(SampleRate);
Context.ResponseData.Write(Channels);
Context.ResponseData.Write((int)SampleFormat.PcmInt16);
Context.ResponseData.Write((int)PlaybackState.Stopped);
return 0;
}
}
}

View file

@ -0,0 +1,169 @@
using Ryujinx.Audio;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services.Aud.AudioRenderer;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Services.Aud
{
class IAudioRendererManager : IpcService
{
private const int Rev0Magic = ('R' << 0) |
('E' << 8) |
('V' << 16) |
('0' << 24);
private const int Rev = 4;
public const int RevMagic = Rev0Magic + (Rev << 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)
{
IAalOutput AudioOut = Context.Device.AudioOut;
AudioRendererParameter Params = GetAudioRendererParameter(Context);
MakeObject(Context, new IAudioRenderer(Context.Memory, AudioOut, Params));
return 0;
}
public long GetAudioRendererWorkBufferSize(ServiceCtx Context)
{
AudioRendererParameter Params = GetAudioRendererParameter(Context);
int Revision = (Params.Revision - Rev0Magic) >> 24;
if (Revision <= Rev)
{
bool IsSplitterSupported = Revision >= 3;
long Size;
Size = IntUtils.AlignUp(Params.Unknown8 * 4, 64);
Size += Params.MixCount * 0x400;
Size += (Params.MixCount + 1) * 0x940;
Size += Params.VoiceCount * 0x3F0;
Size += IntUtils.AlignUp((Params.MixCount + 1) * 8, 16);
Size += IntUtils.AlignUp(Params.VoiceCount * 8, 16);
Size += IntUtils.AlignUp(
((Params.SinkCount + Params.MixCount) * 0x3C0 + Params.SampleCount * 4) *
(Params.Unknown8 + 6), 64);
Size += (Params.SinkCount + Params.MixCount) * 0x2C0;
Size += (Params.EffectCount + Params.VoiceCount * 4) * 0x30 + 0x50;
if (IsSplitterSupported)
{
Size += IntUtils.AlignUp((
NodeStatesGetWorkBufferSize(Params.MixCount + 1) +
EdgeMatrixGetWorkBufferSize(Params.MixCount + 1)), 16);
Size += Params.SplitterDestinationDataCount * 0xE0;
Size += Params.SplitterCount * 0x20;
Size += IntUtils.AlignUp(Params.SplitterDestinationDataCount * 4, 16);
}
Size = Params.EffectCount * 0x4C0 +
Params.SinkCount * 0x170 +
Params.VoiceCount * 0x100 +
IntUtils.AlignUp(Size, 64) + 0x40;
if (Params.PerformanceManagerCount >= 1)
{
Size += (((Params.EffectCount +
Params.SinkCount +
Params.VoiceCount +
Params.MixCount + 1) * 16 + 0x658) *
(Params.PerformanceManagerCount + 1) + 0x13F) & ~0x3FL;
}
Size = (Size + 0x1907D) & ~0xFFFL;
Context.ResponseData.Write(Size);
Context.Device.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{Size:x16}.");
return 0;
}
else
{
Context.ResponseData.Write(0L);
Context.Device.Log.PrintWarning(LogClass.ServiceAudio, $"Library Revision 0x{Params.Revision:x8} is not supported!");
return MakeError(ErrorModule.Audio, AudErr.UnsupportedRevision);
}
}
private AudioRendererParameter GetAudioRendererParameter(ServiceCtx Context)
{
AudioRendererParameter Params = new AudioRendererParameter();
Params.SampleRate = Context.RequestData.ReadInt32();
Params.SampleCount = Context.RequestData.ReadInt32();
Params.Unknown8 = Context.RequestData.ReadInt32();
Params.MixCount = Context.RequestData.ReadInt32();
Params.VoiceCount = Context.RequestData.ReadInt32();
Params.SinkCount = Context.RequestData.ReadInt32();
Params.EffectCount = Context.RequestData.ReadInt32();
Params.PerformanceManagerCount = Context.RequestData.ReadInt32();
Params.VoiceDropEnable = Context.RequestData.ReadInt32();
Params.SplitterCount = Context.RequestData.ReadInt32();
Params.SplitterDestinationDataCount = Context.RequestData.ReadInt32();
Params.Unknown2C = Context.RequestData.ReadInt32();
Params.Revision = Context.RequestData.ReadInt32();
return Params;
}
private static int NodeStatesGetWorkBufferSize(int Value)
{
int Result = IntUtils.AlignUp(Value, 64);
if (Result < 0)
{
Result |= 7;
}
return 4 * (Value * Value) + 0x12 * Value + 2 * (Result / 8);
}
private static int EdgeMatrixGetWorkBufferSize(int Value)
{
int Result = IntUtils.AlignUp(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;
}
}
}

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