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:
parent
182d716867
commit
521751795a
258 changed files with 1574 additions and 1546 deletions
416
Ryujinx.HLE/HOS/Diagnostics/Demangler.cs
Normal file
416
Ryujinx.HLE/HOS/Diagnostics/Demangler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/HOS/ErrorCode.cs
Normal file
10
Ryujinx.HLE/HOS/ErrorCode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
101
Ryujinx.HLE/HOS/ErrorModule.cs
Normal file
101
Ryujinx.HLE/HOS/ErrorModule.cs
Normal 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
|
||||
}
|
||||
}
|
122
Ryujinx.HLE/HOS/Font/SharedFontManager.cs
Normal file
122
Ryujinx.HLE/HOS/Font/SharedFontManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/HOS/Font/SharedFontType.cs
Normal file
13
Ryujinx.HLE/HOS/Font/SharedFontType.cs
Normal 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
|
||||
}
|
||||
}
|
69
Ryujinx.HLE/HOS/GlobalStateTable.cs
Normal file
69
Ryujinx.HLE/HOS/GlobalStateTable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
77
Ryujinx.HLE/HOS/Homebrew.cs
Normal file
77
Ryujinx.HLE/HOS/Homebrew.cs
Normal 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
227
Ryujinx.HLE/HOS/Horizon.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
73
Ryujinx.HLE/HOS/IdDictionary.cs
Normal file
73
Ryujinx.HLE/HOS/IdDictionary.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
27
Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
Normal file
27
Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
92
Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
Normal file
92
Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
140
Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
Normal file
140
Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/HOS/Ipc/IpcMagic.cs
Normal file
8
Ryujinx.HLE/HOS/Ipc/IpcMagic.cs
Normal 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;
|
||||
}
|
||||
}
|
215
Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
Normal file
215
Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
Normal file
12
Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.HLE.HOS.Ipc
|
||||
{
|
||||
enum IpcMessageType
|
||||
{
|
||||
Response = 0,
|
||||
CloseSession = 2,
|
||||
Request = 4,
|
||||
Control = 5,
|
||||
RequestWithContext = 6,
|
||||
ControlWithContext = 7
|
||||
}
|
||||
}
|
26
Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
Normal file
26
Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
Normal file
19
Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
4
Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs
Normal file
4
Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.HOS.Ipc
|
||||
{
|
||||
delegate long ServiceProcessRequest(ServiceCtx Context);
|
||||
}
|
111
Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
Normal file
111
Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
Normal 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
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
Normal file
10
Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
enum AddressSpaceType
|
||||
{
|
||||
Addr32Bits = 0,
|
||||
Addr36Bits = 1,
|
||||
Addr36BitsNoMap = 2,
|
||||
Addr39Bits = 3
|
||||
}
|
||||
}
|
4
Ryujinx.HLE/HOS/Kernel/KEvent.cs
Normal file
4
Ryujinx.HLE/HOS/Kernel/KEvent.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.HLE.HOS.Kernel
|
||||
{
|
||||
class KEvent : KSynchronizationObject { }
|
||||
}
|
43
Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
Normal file
43
Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
33
Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
Normal file
33
Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1081
Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
Normal file
1081
Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
Normal file
File diff suppressed because it is too large
Load diff
34
Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
Normal file
34
Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
370
Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
Normal file
370
Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
Ryujinx.HLE/HOS/Kernel/KSession.cs
Normal file
31
Ryujinx.HLE/HOS/Kernel/KSession.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
Normal file
14
Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
28
Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
Normal file
28
Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
98
Ryujinx.HLE/HOS/Kernel/KThread.cs
Normal file
98
Ryujinx.HLE/HOS/Kernel/KThread.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
Normal file
60
Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
Normal 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--;
|
||||
}
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
Normal file
14
Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
22
Ryujinx.HLE/HOS/Kernel/KernelErr.cs
Normal file
22
Ryujinx.HLE/HOS/Kernel/KernelErr.cs
Normal 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;
|
||||
}
|
||||
}
|
22
Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
Normal file
22
Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
Normal 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
|
||||
}
|
||||
}
|
18
Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
Normal file
18
Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
Normal 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
|
||||
}
|
||||
}
|
49
Ryujinx.HLE/HOS/Kernel/MemoryState.cs
Normal file
49
Ryujinx.HLE/HOS/Kernel/MemoryState.cs
Normal 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
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
Normal file
19
Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
Normal file
48
Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
Normal file
123
Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
577
Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
Normal file
577
Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
373
Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
Normal file
373
Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
385
Ryujinx.HLE/HOS/Kernel/SvcThread.cs
Normal file
385
Ryujinx.HLE/HOS/Kernel/SvcThread.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
523
Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
Normal file
523
Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
158
Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
Normal file
158
Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
Normal 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
438
Ryujinx.HLE/HOS/Process.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
Ryujinx.HLE/HOS/ServiceCtx.cs
Normal file
39
Ryujinx.HLE/HOS/ServiceCtx.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
7
Ryujinx.HLE/HOS/Services/Acc/AccErr.cs
Normal file
7
Ryujinx.HLE/HOS/Services/Acc/AccErr.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Acc
|
||||
{
|
||||
static class AccErr
|
||||
{
|
||||
public const int UserNotFound = 100;
|
||||
}
|
||||
}
|
125
Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs
Normal file
125
Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs
Normal file
38
Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
58
Ryujinx.HLE/HOS/Services/Acc/IProfile.cs
Normal file
58
Ryujinx.HLE/HOS/Services/Acc/IProfile.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
7
Ryujinx.HLE/HOS/Services/Am/AmErr.cs
Normal file
7
Ryujinx.HLE/HOS/Services/Am/AmErr.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Am
|
||||
{
|
||||
static class AmErr
|
||||
{
|
||||
public const int NoMessages = 3;
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/HOS/Services/Am/FocusState.cs
Normal file
8
Ryujinx.HLE/HOS/Services/Am/FocusState.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Am
|
||||
{
|
||||
enum FocusState
|
||||
{
|
||||
InFocus = 1,
|
||||
OutOfFocus = 2
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs
Normal 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>()
|
||||
{
|
||||
//...
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
117
Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs
Normal file
117
Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
83
Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs
Normal file
83
Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
27
Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs
Normal file
27
Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.HLE/HOS/Services/Am/IAudioController.cs
Normal file
72
Ryujinx.HLE/HOS/Services/Am/IAudioController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
115
Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs
Normal file
115
Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs
Normal 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>()
|
||||
{
|
||||
//...
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs
Normal 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>()
|
||||
{
|
||||
//...
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs
Normal 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>()
|
||||
{
|
||||
//...
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
46
Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs
Normal file
46
Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
71
Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs
Normal file
71
Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
37
Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs
Normal file
37
Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
145
Ryujinx.HLE/HOS/Services/Am/ISelfController.cs
Normal file
145
Ryujinx.HLE/HOS/Services/Am/ISelfController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
31
Ryujinx.HLE/HOS/Services/Am/IStorage.cs
Normal file
31
Ryujinx.HLE/HOS/Services/Am/IStorage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
83
Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs
Normal file
83
Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
99
Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs
Normal file
99
Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
Ryujinx.HLE/HOS/Services/Am/IWindowController.cs
Normal file
38
Ryujinx.HLE/HOS/Services/Am/IWindowController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs
Normal file
9
Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Am
|
||||
{
|
||||
enum MessageInfo
|
||||
{
|
||||
FocusStateChanged = 0xf,
|
||||
OperationModeChanged = 0x1e,
|
||||
PerformanceModeChanged = 0x1f
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/HOS/Services/Am/OperationMode.cs
Normal file
8
Ryujinx.HLE/HOS/Services/Am/OperationMode.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Am
|
||||
{
|
||||
enum OperationMode
|
||||
{
|
||||
Handheld = 0,
|
||||
Docked = 1
|
||||
}
|
||||
}
|
27
Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs
Normal file
27
Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
Ryujinx.HLE/HOS/Services/Apm/IManager.cs
Normal file
27
Ryujinx.HLE/HOS/Services/Apm/IManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.HLE/HOS/Services/Apm/ISession.cs
Normal file
41
Ryujinx.HLE/HOS/Services/Apm/ISession.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
18
Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs
Normal file
18
Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs
Normal 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
|
||||
}
|
||||
}
|
8
Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs
Normal file
8
Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Apm
|
||||
{
|
||||
enum PerformanceMode
|
||||
{
|
||||
Handheld = 0,
|
||||
Docked = 1
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Services/Aud/AudErr.cs
Normal file
9
Ryujinx.HLE/HOS/Services/Aud/AudErr.cs
Normal 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;
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs
Normal file
14
Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs
Normal 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;
|
||||
}
|
||||
}
|
163
Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs
Normal file
163
Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
||||
{
|
||||
static class AudioConsts
|
||||
{
|
||||
public const int HostSampleRate = 48000;
|
||||
public const int HostChannelsCount = 2;
|
||||
}
|
||||
}
|
11
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs
Normal file
11
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs
Normal 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;
|
||||
}
|
||||
}
|
16
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs
Normal file
16
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs
Normal 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;
|
||||
}
|
||||
}
|
318
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs
Normal file
318
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
||||
{
|
||||
class MemoryPoolContext
|
||||
{
|
||||
public MemoryPoolOut OutStatus;
|
||||
|
||||
public MemoryPoolContext()
|
||||
{
|
||||
OutStatus.State = MemoryPoolState.Detached;
|
||||
}
|
||||
}
|
||||
}
|
14
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs
Normal file
14
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs
Normal 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;
|
||||
}
|
||||
}
|
12
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs
Normal file
12
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs
Normal file
9
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
||||
{
|
||||
enum PlayState : byte
|
||||
{
|
||||
Playing = 0,
|
||||
Stopped = 1,
|
||||
Paused = 2
|
||||
}
|
||||
}
|
191
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs
Normal file
191
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
|
||||
struct VoiceChannelResourceIn
|
||||
{
|
||||
//???
|
||||
}
|
||||
}
|
188
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs
Normal file
188
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
49
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs
Normal file
49
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs
Normal 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;
|
||||
}
|
||||
}
|
12
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs
Normal file
12
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs
Normal 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; //?
|
||||
}
|
||||
}
|
20
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs
Normal file
20
Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs
Normal 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;
|
||||
}
|
||||
}
|
22
Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs
Normal file
22
Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs
Normal 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;
|
||||
}
|
||||
}
|
223
Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs
Normal file
223
Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
170
Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs
Normal file
170
Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
169
Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs
Normal file
169
Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs
Normal 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
Loading…
Add table
Add a link
Reference in a new issue