aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Core
diff options
context:
space:
mode:
authoremmauss <emmausssss@gmail.com>2018-02-20 22:09:23 +0200
committergdkchan <gab.dark.100@gmail.com>2018-02-20 17:09:23 -0300
commit62b827f474f0aa2152dd339fcc7cf31084e16a0b (patch)
tree0e5c55b341aee4db0ccb841a084f253ec5e05657 /Ryujinx.Core
parentcb665bb715834526d73c9469d16114b287faaecd (diff)
Split main project into core,graphics and chocolarm4 subproject (#29)
Diffstat (limited to 'Ryujinx.Core')
-rw-r--r--Ryujinx.Core/Config.cs95
-rw-r--r--Ryujinx.Core/Hid.cs185
-rw-r--r--Ryujinx.Core/Hid/HidController.cs188
-rw-r--r--Ryujinx.Core/Hid/HidKeyboard.cs33
-rw-r--r--Ryujinx.Core/Hid/HidMouse.cs37
-rw-r--r--Ryujinx.Core/Hid/HidTouchScreen.cs54
-rw-r--r--Ryujinx.Core/Hid/HidUnknown.cs81
-rw-r--r--Ryujinx.Core/Hid/JoyCon.cs66
-rw-r--r--Ryujinx.Core/Loaders/Compression/Lz4.cs78
-rw-r--r--Ryujinx.Core/Loaders/ElfDyn.cs15
-rw-r--r--Ryujinx.Core/Loaders/ElfDynTag.cs72
-rw-r--r--Ryujinx.Core/Loaders/ElfRel.cs19
-rw-r--r--Ryujinx.Core/Loaders/ElfRelType.cs128
-rw-r--r--Ryujinx.Core/Loaders/ElfSym.cs43
-rw-r--r--Ryujinx.Core/Loaders/ElfSymBinding.cs9
-rw-r--r--Ryujinx.Core/Loaders/ElfSymType.cs13
-rw-r--r--Ryujinx.Core/Loaders/ElfSymVisibility.cs10
-rw-r--r--Ryujinx.Core/Loaders/Executable.cs149
-rw-r--r--Ryujinx.Core/Loaders/Executables/IExecutable.cs17
-rw-r--r--Ryujinx.Core/Loaders/Executables/Nro.cs62
-rw-r--r--Ryujinx.Core/Loaders/Executables/Nso.cs122
-rw-r--r--Ryujinx.Core/Logging.cs132
-rw-r--r--Ryujinx.Core/OsHle/CondVar.cs138
-rw-r--r--Ryujinx.Core/OsHle/Display.cs12
-rw-r--r--Ryujinx.Core/OsHle/Exceptions/GuestBrokeExecutionException.cs11
-rw-r--r--Ryujinx.Core/OsHle/Exceptions/UndefinedInstructionException.cs13
-rw-r--r--Ryujinx.Core/OsHle/FileDesc.cs12
-rw-r--r--Ryujinx.Core/OsHle/Handles/HDomain.cs58
-rw-r--r--Ryujinx.Core/OsHle/Handles/HEvent.cs7
-rw-r--r--Ryujinx.Core/OsHle/Handles/HNvMap.cs18
-rw-r--r--Ryujinx.Core/OsHle/Handles/HSession.cs27
-rw-r--r--Ryujinx.Core/OsHle/Handles/HSessionObj.cs30
-rw-r--r--Ryujinx.Core/OsHle/Handles/HSharedMem.cs70
-rw-r--r--Ryujinx.Core/OsHle/Handles/HThread.cs21
-rw-r--r--Ryujinx.Core/OsHle/Handles/HTransferMem.cs21
-rw-r--r--Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs334
-rw-r--r--Ryujinx.Core/OsHle/Horizon.cs197
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcBuffDesc.cs27
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs8
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcHandleDesc.cs90
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcHandler.cs277
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcMessage.cs231
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcMessageType.cs10
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs26
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcRecvListBuffDesc.cs19
-rw-r--r--Ryujinx.Core/OsHle/Ipc/ServiceProcessRequest.cs4
-rw-r--r--Ryujinx.Core/OsHle/MemoryInfo.cs28
-rw-r--r--Ryujinx.Core/OsHle/MemoryType.cs25
-rw-r--r--Ryujinx.Core/OsHle/Mutex.cs122
-rw-r--r--Ryujinx.Core/OsHle/Objects/Acc/IManagerForApplication.cs33
-rw-r--r--Ryujinx.Core/OsHle/Objects/Acc/IProfile.cs33
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IApplicationFunctions.cs80
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IApplicationProxy.cs85
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IAudioController.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/ICommonStateGetter.cs74
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IDebugFunctions.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IDisplayController.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/ILibraryAppletCreator.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IParentalControlService.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/ISelfController.cs53
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IStorage.cs33
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IStorageAccessor.cs62
-rw-r--r--Ryujinx.Core/OsHle/Objects/Am/IWindowController.cs33
-rw-r--r--Ryujinx.Core/OsHle/Objects/Apm/ISession.cs28
-rw-r--r--Ryujinx.Core/OsHle/Objects/Aud/IAudioOut.cs180
-rw-r--r--Ryujinx.Core/OsHle/Objects/Aud/IAudioRenderer.cs66
-rw-r--r--Ryujinx.Core/OsHle/Objects/Friend/IFriendService.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/FspSrv/IDirectory.cs133
-rw-r--r--Ryujinx.Core/OsHle/Objects/FspSrv/IFile.cs93
-rw-r--r--Ryujinx.Core/OsHle/Objects/FspSrv/IFileSystem.cs247
-rw-r--r--Ryujinx.Core/OsHle/Objects/FspSrv/IStorage.cs52
-rw-r--r--Ryujinx.Core/OsHle/Objects/Hid/IAppletResource.cs32
-rw-r--r--Ryujinx.Core/OsHle/Objects/IIpcInterface.cs10
-rw-r--r--Ryujinx.Core/OsHle/Objects/ObjHelper.cs24
-rw-r--r--Ryujinx.Core/OsHle/Objects/Parcel.cs58
-rw-r--r--Ryujinx.Core/OsHle/Objects/Time/ISteadyClock.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Time/ISystemClock.cs42
-rw-r--r--Ryujinx.Core/OsHle/Objects/Time/ITimeZoneService.cs20
-rw-r--r--Ryujinx.Core/OsHle/Objects/Time/SystemClockType.cs9
-rw-r--r--Ryujinx.Core/OsHle/Objects/Vi/IApplicationDisplayService.cs176
-rw-r--r--Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs214
-rw-r--r--Ryujinx.Core/OsHle/Objects/Vi/IManagerDisplayService.cs33
-rw-r--r--Ryujinx.Core/OsHle/Objects/Vi/ISystemDisplayService.cs25
-rw-r--r--Ryujinx.Core/OsHle/Process.cs257
-rw-r--r--Ryujinx.Core/OsHle/ServiceCtx.cs36
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceAcc.cs33
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceApm.cs16
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceAppletOE.cs16
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceAud.cs71
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceFriend.cs16
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceFspSrv.cs49
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceHid.cs56
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceLm.cs12
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceNvDrv.cs610
-rw-r--r--Ryujinx.Core/OsHle/Services/ServicePctl.cs16
-rw-r--r--Ryujinx.Core/OsHle/Services/ServicePl.cs35
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceSet.cs32
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceSm.cs48
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceTime.cs45
-rw-r--r--Ryujinx.Core/OsHle/Services/ServiceVi.cs18
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcHandler.cs80
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcMemory.cs121
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcResult.cs11
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcSystem.cs227
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcThread.cs85
-rw-r--r--Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs78
-rw-r--r--Ryujinx.Core/OsHle/Utilities/IdPool.cs53
-rw-r--r--Ryujinx.Core/OsHle/Utilities/IdPoolWithObj.cs78
-rw-r--r--Ryujinx.Core/OsHle/Utilities/MemReader.cs44
-rw-r--r--Ryujinx.Core/OsHle/Utilities/MemWriter.cs38
-rw-r--r--Ryujinx.Core/Ryujinx.Core.csproj20
-rw-r--r--Ryujinx.Core/Switch.cs75
-rw-r--r--Ryujinx.Core/VirtualFs.cs70
113 files changed, 7858 insertions, 0 deletions
diff --git a/Ryujinx.Core/Config.cs b/Ryujinx.Core/Config.cs
new file mode 100644
index 00000000..b97e80b8
--- /dev/null
+++ b/Ryujinx.Core/Config.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Ryujinx.Core
+{
+ public static class Config
+ {
+ public static bool LoggingEnableInfo { get; private set; }
+ public static bool LoggingEnableTrace { get; private set; }
+ public static bool LoggingEnableDebug { get; private set; }
+ public static bool LoggingEnableWarn { get; private set; }
+ public static bool LoggingEnableError { get; private set; }
+ public static bool LoggingEnableFatal { get; private set; }
+ public static bool LoggingEnableLogFile { get; private set; }
+
+ public static JoyCon FakeJoyCon { get; private set; }
+
+ public static void Read()
+ {
+ var iniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ var iniPath = Path.Combine(iniFolder, "Ryujinx.conf");
+ IniParser Parser = new IniParser(iniPath);
+
+ LoggingEnableInfo = Convert.ToBoolean(Parser.Value("Logging_Enable_Info"));
+ LoggingEnableTrace = Convert.ToBoolean(Parser.Value("Logging_Enable_Trace"));
+ LoggingEnableDebug = Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"));
+ LoggingEnableWarn = Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"));
+ LoggingEnableError = Convert.ToBoolean(Parser.Value("Logging_Enable_Error"));
+ LoggingEnableFatal = Convert.ToBoolean(Parser.Value("Logging_Enable_Fatal"));
+ LoggingEnableLogFile = Convert.ToBoolean(Parser.Value("Logging_Enable_LogFile"));
+
+ FakeJoyCon = new JoyCon
+ {
+ Left = new JoyConLeft
+ {
+ StickUp = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Up")),
+ StickDown = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Down")),
+ StickLeft = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Left")),
+ StickRight = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Right")),
+ StickButton = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Stick_Button")),
+ DPadUp = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Up")),
+ DPadDown = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Down")),
+ DPadLeft = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Left")),
+ DPadRight = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_DPad_Right")),
+ ButtonMinus = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_Minus")),
+ ButtonL = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_L")),
+ ButtonZL = Convert.ToInt16(Parser.Value("Controls_Left_FakeJoycon_Button_ZL"))
+ },
+
+ Right = new JoyConRight
+ {
+ StickUp = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Up")),
+ StickDown = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Down")),
+ StickLeft = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Left")),
+ StickRight = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Right")),
+ StickButton = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Stick_Button")),
+ ButtonA = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_A")),
+ ButtonB = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_B")),
+ ButtonX = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_X")),
+ ButtonY = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_Y")),
+ ButtonPlus = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_Plus")),
+ ButtonR = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_R")),
+ ButtonZR = Convert.ToInt16(Parser.Value("Controls_Right_FakeJoycon_Button_ZR"))
+ }
+ };
+ }
+ }
+
+ // https://stackoverflow.com/a/37772571
+ public class IniParser
+ {
+ private readonly Dictionary<string, string> Values;
+
+ public IniParser(string Path)
+ {
+ Values = File.ReadLines(Path)
+ .Where(Line => !string.IsNullOrWhiteSpace(Line) && !Line.StartsWith('#'))
+ .Select(Line => Line.Split('=', 2))
+ .ToDictionary(Parts => Parts[0].Trim(), Parts => Parts.Length > 1 ? Parts[1].Trim() : null);
+ }
+
+ /// <summary>
+ /// Gets the setting value for the requested setting <see cref="Name"/>.
+ /// </summary>
+ /// <param name="Name">Setting Name</param>
+ /// <param name="defaultValue">Default value of the setting</param>
+ public string Value(string Name, string defaultValue = null)
+ {
+ return Values.TryGetValue(Name, out var value) ? value : defaultValue;
+ }
+ }
+}
diff --git a/Ryujinx.Core/Hid.cs b/Ryujinx.Core/Hid.cs
new file mode 100644
index 00000000..44d3e0fb
--- /dev/null
+++ b/Ryujinx.Core/Hid.cs
@@ -0,0 +1,185 @@
+using Ryujinx.Core.OsHle;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ public class Hid
+ {
+ /*
+ Thanks to:
+ https://github.com/reswitched/libtransistor/blob/development/lib/hid.c
+ https://github.com/reswitched/libtransistor/blob/development/include/libtransistor/hid.h
+ https://github.com/switchbrew/libnx/blob/master/nx/source/services/hid.c
+ https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h
+
+ struct HidSharedMemory
+ {
+ header[0x400];
+ touchscreen[0x3000];
+ mouse[0x400];
+ keyboard[0x400];
+ unkSection1[0x400];
+ unkSection2[0x400];
+ unkSection3[0x400];
+ unkSection4[0x400];
+ unkSection5[0x200];
+ unkSection6[0x200];
+ unkSection7[0x200];
+ unkSection8[0x800];
+ controllerSerials[0x4000];
+ controllers[0x5000 * 10];
+ unkSection9[0x4600];
+ }
+ */
+
+ private const int Hid_Num_Entries = 16;
+ private Switch Ns;
+ private long SharedMemOffset;
+
+ public Hid(Switch Ns)
+ {
+ this.Ns = Ns;
+ }
+
+ public void Init(long HidOffset)
+ {
+ unsafe
+ {
+ if (HidOffset == 0 || HidOffset + Horizon.HidSize > uint.MaxValue)
+ {
+ return;
+ }
+
+ SharedMemOffset = HidOffset;
+
+ uint InnerOffset = (uint)Marshal.SizeOf(typeof(HidSharedMemHeader));
+
+ IntPtr HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset);
+
+ HidTouchScreen TouchScreen = new HidTouchScreen();
+ TouchScreen.Header.TimestampTicks = (ulong)Environment.TickCount;
+ TouchScreen.Header.NumEntries = (ulong)Hid_Num_Entries;
+ TouchScreen.Header.LatestEntry = 0;
+ TouchScreen.Header.MaxEntryIndex = (ulong)Hid_Num_Entries - 1;
+ TouchScreen.Header.Timestamp = (ulong)Environment.TickCount;
+
+ //TODO: Write this structure when the input is implemented
+ //Marshal.StructureToPtr(TouchScreen, HidPtr, false);
+
+ InnerOffset += (uint)Marshal.SizeOf(typeof(HidTouchScreen));
+ HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset);
+
+ HidMouse Mouse = new HidMouse();
+ Mouse.Header.TimestampTicks = (ulong)Environment.TickCount;
+ Mouse.Header.NumEntries = (ulong)Hid_Num_Entries;
+ Mouse.Header.LatestEntry = 0;
+ Mouse.Header.MaxEntryIndex = (ulong)Hid_Num_Entries - 1;
+
+ //TODO: Write this structure when the input is implemented
+ //Marshal.StructureToPtr(Mouse, HidPtr, false);
+
+ InnerOffset += (uint)Marshal.SizeOf(typeof(HidMouse));
+ HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset);
+
+ HidKeyboard Keyboard = new HidKeyboard();
+ Keyboard.Header.TimestampTicks = (ulong)Environment.TickCount;
+ Keyboard.Header.NumEntries = (ulong)Hid_Num_Entries;
+ Keyboard.Header.LatestEntry = 0;
+ Keyboard.Header.MaxEntryIndex = (ulong)Hid_Num_Entries - 1;
+
+ //TODO: Write this structure when the input is implemented
+ //Marshal.StructureToPtr(Keyboard, HidPtr, false);
+
+ InnerOffset += (uint)Marshal.SizeOf(typeof(HidKeyboard)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection1)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection2)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection3)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection4)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection5)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection6)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection7)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection8)) +
+ (uint)Marshal.SizeOf(typeof(HidControllerSerials));
+
+ //Increase the loop to initialize more controller.
+ for (int i = 8; i < Enum.GetNames(typeof(HidControllerID)).Length - 1; i++)
+ {
+ HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset + (uint)(Marshal.SizeOf(typeof(HidController)) * i));
+
+ HidController Controller = new HidController();
+ Controller.Header.Type = (uint)(HidControllerType.ControllerType_Handheld | HidControllerType.ControllerType_JoyconPair);
+ Controller.Header.IsHalf = 0;
+ Controller.Header.SingleColorsDescriptor = (uint)(HidControllerColorDescription.ColorDesc_ColorsNonexistent);
+ Controller.Header.SingleColorBody = 0;
+ Controller.Header.SingleColorButtons = 0;
+ Controller.Header.SplitColorsDescriptor = 0;
+ Controller.Header.LeftColorBody = (uint)JoyConColor.Body_Neon_Red;
+ Controller.Header.LeftColorButtons = (uint)JoyConColor.Buttons_Neon_Red;
+ Controller.Header.RightColorBody = (uint)JoyConColor.Body_Neon_Blue;
+ Controller.Header.RightColorButtons = (uint)JoyConColor.Buttons_Neon_Blue;
+
+ Controller.Layouts = new HidControllerLayout[Enum.GetNames(typeof(HidControllerLayouts)).Length];
+ Controller.Layouts[(int)HidControllerLayouts.Main] = new HidControllerLayout();
+ Controller.Layouts[(int)HidControllerLayouts.Main].Header.LatestEntry = (ulong)Hid_Num_Entries;
+
+ Marshal.StructureToPtr(Controller, HidPtr, false);
+ }
+
+ Logging.Info("HID Initialized!");
+ }
+ }
+
+ public void SendControllerButtons(HidControllerID ControllerId,
+ HidControllerLayouts Layout,
+ HidControllerKeys Buttons,
+ JoystickPosition LeftJoystick,
+ JoystickPosition RightJoystick)
+ {
+ uint InnerOffset = (uint)Marshal.SizeOf(typeof(HidSharedMemHeader)) +
+ (uint)Marshal.SizeOf(typeof(HidTouchScreen)) +
+ (uint)Marshal.SizeOf(typeof(HidMouse)) +
+ (uint)Marshal.SizeOf(typeof(HidKeyboard)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection1)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection2)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection3)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection4)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection5)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection6)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection7)) +
+ (uint)Marshal.SizeOf(typeof(HidUnknownSection8)) +
+ (uint)Marshal.SizeOf(typeof(HidControllerSerials)) +
+ ((uint)(Marshal.SizeOf(typeof(HidController)) * (int)ControllerId)) +
+ (uint)Marshal.SizeOf(typeof(HidControllerHeader)) +
+ (uint)Layout * (uint)Marshal.SizeOf(typeof(HidControllerLayout));
+
+ IntPtr HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset);
+
+ HidControllerLayoutHeader OldControllerHeaderLayout = (HidControllerLayoutHeader)Marshal.PtrToStructure(HidPtr, typeof(HidControllerLayoutHeader));
+
+ HidControllerLayoutHeader ControllerLayoutHeader = new HidControllerLayoutHeader
+ {
+ TimestampTicks = (ulong)Environment.TickCount,
+ NumEntries = (ulong)Hid_Num_Entries,
+ MaxEntryIndex = (ulong)Hid_Num_Entries - 1,
+ LatestEntry = (OldControllerHeaderLayout.LatestEntry < (ulong)Hid_Num_Entries ? OldControllerHeaderLayout.LatestEntry + 1 : 0)
+ };
+
+ Marshal.StructureToPtr(ControllerLayoutHeader, HidPtr, false);
+
+ InnerOffset += (uint)Marshal.SizeOf(typeof(HidControllerLayoutHeader)) + (uint)((uint)(ControllerLayoutHeader.LatestEntry) * Marshal.SizeOf(typeof(HidControllerInputEntry)));
+ HidPtr = new IntPtr(Ns.Ram.ToInt64() + (uint)SharedMemOffset + InnerOffset);
+
+ HidControllerInputEntry ControllerInputEntry = new HidControllerInputEntry();
+ ControllerInputEntry.Timestamp = (ulong)Environment.TickCount;
+ ControllerInputEntry.Timestamp_2 = (ulong)Environment.TickCount;
+ ControllerInputEntry.Buttons = (ulong)Buttons;
+ ControllerInputEntry.Joysticks = new JoystickPosition[(int)HidControllerJoystick.Joystick_Num_Sticks];
+ ControllerInputEntry.Joysticks[(int)HidControllerJoystick.Joystick_Left] = LeftJoystick;
+ ControllerInputEntry.Joysticks[(int)HidControllerJoystick.Joystick_Right] = RightJoystick;
+ ControllerInputEntry.ConnectionState = (ulong)(HidControllerConnectionState.Controller_State_Connected | HidControllerConnectionState.Controller_State_Wired);
+
+ Marshal.StructureToPtr(ControllerInputEntry, HidPtr, false);
+ }
+ }
+}
diff --git a/Ryujinx.Core/Hid/HidController.cs b/Ryujinx.Core/Hid/HidController.cs
new file mode 100644
index 00000000..641bc556
--- /dev/null
+++ b/Ryujinx.Core/Hid/HidController.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ [Flags]
+ public enum HidControllerKeys
+ {
+ KEY_A = (1 << 0),
+ KEY_B = (1 << 1),
+ KEY_X = (1 << 2),
+ KEY_Y = (1 << 3),
+ KEY_LSTICK = (1 << 4),
+ KEY_RSTICK = (1 << 5),
+ KEY_L = (1 << 6),
+ KEY_R = (1 << 7),
+ KEY_ZL = (1 << 8),
+ KEY_ZR = (1 << 9),
+ KEY_PLUS = (1 << 10),
+ KEY_MINUS = (1 << 11),
+ KEY_DLEFT = (1 << 12),
+ KEY_DUP = (1 << 13),
+ KEY_DRIGHT = (1 << 14),
+ KEY_DDOWN = (1 << 15),
+ KEY_LSTICK_LEFT = (1 << 16),
+ KEY_LSTICK_UP = (1 << 17),
+ KEY_LSTICK_RIGHT = (1 << 18),
+ KEY_LSTICK_DOWN = (1 << 19),
+ KEY_RSTICK_LEFT = (1 << 20),
+ KEY_RSTICK_UP = (1 << 21),
+ KEY_RSTICK_RIGHT = (1 << 22),
+ KEY_RSTICK_DOWN = (1 << 23),
+ KEY_SL = (1 << 24),
+ KEY_SR = (1 << 25),
+
+ // Pseudo-key for at least one finger on the touch screen
+ KEY_TOUCH = (1 << 26),
+
+ // Buttons by orientation (for single Joy-Con), also works with Joy-Con pairs, Pro Controller
+ KEY_JOYCON_RIGHT = (1 << 0),
+ KEY_JOYCON_DOWN = (1 << 1),
+ KEY_JOYCON_UP = (1 << 2),
+ KEY_JOYCON_LEFT = (1 << 3),
+
+ // Generic catch-all directions, also works for single Joy-Con
+ KEY_UP = KEY_DUP | KEY_LSTICK_UP | KEY_RSTICK_UP,
+ KEY_DOWN = KEY_DDOWN | KEY_LSTICK_DOWN | KEY_RSTICK_DOWN,
+ KEY_LEFT = KEY_DLEFT | KEY_LSTICK_LEFT | KEY_RSTICK_LEFT,
+ KEY_RIGHT = KEY_DRIGHT | KEY_LSTICK_RIGHT | KEY_RSTICK_RIGHT,
+ }
+
+ public enum HidControllerID
+ {
+ CONTROLLER_PLAYER_1 = 0,
+ CONTROLLER_PLAYER_2 = 1,
+ CONTROLLER_PLAYER_3 = 2,
+ CONTROLLER_PLAYER_4 = 3,
+ CONTROLLER_PLAYER_5 = 4,
+ CONTROLLER_PLAYER_6 = 5,
+ CONTROLLER_PLAYER_7 = 6,
+ CONTROLLER_PLAYER_8 = 7,
+ CONTROLLER_HANDHELD = 8,
+ CONTROLLER_UNKNOWN = 9
+ }
+
+ public enum HidControllerJoystick
+ {
+ Joystick_Left = 0,
+ Joystick_Right = 1,
+ Joystick_Num_Sticks = 2
+ }
+
+ public enum HidControllerLayouts
+ {
+ Pro_Controller,
+ Handheld_Joined,
+ Joined,
+ Left,
+ Right,
+ Main_No_Analog,
+ Main
+ }
+
+ [Flags]
+ public enum HidControllerConnectionState
+ {
+ Controller_State_Connected = (1 << 0),
+ Controller_State_Wired = (1 << 1)
+ }
+
+ [Flags]
+ public enum HidControllerType
+ {
+ ControllerType_ProController = (1 << 0),
+ ControllerType_Handheld = (1 << 1),
+ ControllerType_JoyconPair = (1 << 2),
+ ControllerType_JoyconLeft = (1 << 3),
+ ControllerType_JoyconRight = (1 << 4)
+ }
+
+ public enum HidControllerColorDescription
+ {
+ ColorDesc_ColorsNonexistent = (1 << 1),
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x8)]
+ public struct JoystickPosition
+ {
+ public int DX;
+ public int DY;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+ public struct HidControllerMAC
+ {
+ public ulong Timestamp;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public byte[] MAC;
+ public ulong Unknown;
+ public ulong Timestamp_2;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+ public struct HidControllerHeader
+ {
+ public uint Type;
+ public uint IsHalf;
+ public uint SingleColorsDescriptor;
+ public uint SingleColorBody;
+ public uint SingleColorButtons;
+ public uint SplitColorsDescriptor;
+ public uint LeftColorBody;
+ public uint LeftColorButtons;
+ public uint RightColorBody;
+ public uint RightColorButtons;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+ public struct HidControllerLayoutHeader
+ {
+ public ulong TimestampTicks;
+ public ulong NumEntries;
+ public ulong LatestEntry;
+ public ulong MaxEntryIndex;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x30)]
+ public struct HidControllerInputEntry
+ {
+ public ulong Timestamp;
+ public ulong Timestamp_2;
+ public ulong Buttons;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)HidControllerJoystick.Joystick_Num_Sticks)]
+ public JoystickPosition[] Joysticks;
+ public ulong ConnectionState;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x350)]
+ public struct HidControllerLayout
+ {
+ public HidControllerLayoutHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
+ public HidControllerInputEntry[] Entries;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x5000)]
+ public struct HidController
+ {
+ public HidControllerHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
+ public HidControllerLayout[] Layouts;
+ /*
+ pro_controller
+ handheld_joined
+ joined
+ left
+ right
+ main_no_analog
+ main
+ */
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x2A70)]
+ public byte[] Unknown_1;
+ public HidControllerMAC MacLeft;
+ public HidControllerMAC MacRight;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xDF8)]
+ public byte[] Unknown_2;
+ }
+}
diff --git a/Ryujinx.Core/Hid/HidKeyboard.cs b/Ryujinx.Core/Hid/HidKeyboard.cs
new file mode 100644
index 00000000..f9987f68
--- /dev/null
+++ b/Ryujinx.Core/Hid/HidKeyboard.cs
@@ -0,0 +1,33 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+ public struct HidKeyboardHeader
+ {
+ public ulong TimestampTicks;
+ public ulong NumEntries;
+ public ulong LatestEntry;
+ public ulong MaxEntryIndex;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x38)]
+ public struct HidKeyboardEntry
+ {
+ public ulong Timestamp;
+ public ulong Timestamp_2;
+ public ulong Modifier;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public uint[] Keys;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidKeyboard
+ {
+ public HidKeyboardHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
+ public HidKeyboardEntry[] Entries;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x28)]
+ public byte[] Padding;
+ }
+}
diff --git a/Ryujinx.Core/Hid/HidMouse.cs b/Ryujinx.Core/Hid/HidMouse.cs
new file mode 100644
index 00000000..9a019dd5
--- /dev/null
+++ b/Ryujinx.Core/Hid/HidMouse.cs
@@ -0,0 +1,37 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+ public struct HidMouseHeader
+ {
+ public ulong TimestampTicks;
+ public ulong NumEntries;
+ public ulong LatestEntry;
+ public ulong MaxEntryIndex;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x30)]
+ public struct HidMouseEntry
+ {
+ public ulong Timestamp;
+ public ulong Timestamp_2;
+ public uint X;
+ public uint Y;
+ public uint VelocityX;
+ public uint VelocityY;
+ public uint ScrollVelocityX;
+ public uint ScrollVelocityY;
+ public ulong Buttons;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidMouse
+ {
+ public HidMouseHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
+ public HidMouseEntry[] Entries;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xB0)]
+ public byte[] Padding;
+ }
+}
diff --git a/Ryujinx.Core/Hid/HidTouchScreen.cs b/Ryujinx.Core/Hid/HidTouchScreen.cs
new file mode 100644
index 00000000..b755cb95
--- /dev/null
+++ b/Ryujinx.Core/Hid/HidTouchScreen.cs
@@ -0,0 +1,54 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+ public struct HidTouchScreenHeader
+ {
+ public ulong TimestampTicks;
+ public ulong NumEntries;
+ public ulong LatestEntry;
+ public ulong MaxEntryIndex;
+ public ulong Timestamp;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x10)]
+ public struct HidTouchScreenEntryHeader
+ {
+ public ulong Timestamp;
+ public ulong NumTouches;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+ public struct HidTouchScreenEntryTouch
+ {
+ public ulong Timestamp;
+ public uint Padding;
+ public uint TouchIndex;
+ public uint X;
+ public uint Y;
+ public uint DiameterX;
+ public uint DiameterY;
+ public uint Angle;
+ public uint Padding_2;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x298)]
+ public struct HidTouchScreenEntry
+ {
+ public HidTouchScreenEntryHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
+ public HidTouchScreenEntryTouch[] Touches;
+ public ulong Unknown;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x3000)]
+ public struct HidTouchScreen
+ {
+ public HidTouchScreenHeader Header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
+ public HidTouchScreenEntry[] Entries;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3C0)]
+ public byte[] Padding;
+ }
+}
diff --git a/Ryujinx.Core/Hid/HidUnknown.cs b/Ryujinx.Core/Hid/HidUnknown.cs
new file mode 100644
index 00000000..c3fe68a8
--- /dev/null
+++ b/Ryujinx.Core/Hid/HidUnknown.cs
@@ -0,0 +1,81 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidSharedMemHeader
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidUnknownSection1
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidUnknownSection2
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidUnknownSection3
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+ public struct HidUnknownSection4
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x200)]
+ public struct HidUnknownSection5
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x200)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x200)]
+ public struct HidUnknownSection6
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x200)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x200)]
+ public struct HidUnknownSection7
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x200)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x800)]
+ public struct HidUnknownSection8
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x800)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x4000)]
+ public struct HidControllerSerials
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4000)]
+ public byte[] Padding;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 0x4600)]
+ public struct HidUnknownSection9
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4600)]
+ public byte[] Padding;
+ }
+}
diff --git a/Ryujinx.Core/Hid/JoyCon.cs b/Ryujinx.Core/Hid/JoyCon.cs
new file mode 100644
index 00000000..8c9518e5
--- /dev/null
+++ b/Ryujinx.Core/Hid/JoyCon.cs
@@ -0,0 +1,66 @@
+namespace Ryujinx
+{
+ /// <summary>
+ /// Common RGB color hex codes for JoyCon coloring.
+ /// </summary>
+ public enum JoyConColor //Thanks to CTCaer
+ {
+ Body_Grey = 0x828282,
+ Body_Neon_Blue = 0x0AB9E6,
+ Body_Neon_Red = 0xFF3C28,
+ Body_Neon_Yellow = 0xE6FF00,
+ Body_Neon_Pink = 0xFF3278,
+ Body_Neon_Green = 0x1EDC00,
+ Body_Red = 0xE10F00,
+
+ Buttons_Grey = 0x0F0F0F,
+ Buttons_Neon_Blue = 0x001E1E,
+ Buttons_Neon_Red = 0x1E0A0A,
+ Buttons_Neon_Yellow = 0x142800,
+ Buttons_Neon_Pink = 0x28001E,
+ Buttons_Neon_Green = 0x002800,
+ Buttons_Red = 0x280A0A
+ }
+
+ public struct JoyConLeft
+ {
+ public int StickUp;
+ public int StickDown;
+ public int StickLeft;
+ public int StickRight;
+ public int StickButton;
+ public int DPadUp;
+ public int DPadDown;
+ public int DPadLeft;
+ public int DPadRight;
+ public int ButtonMinus;
+ public int ButtonL;
+ public int ButtonZL;
+ public int ButtonSL;
+ public int ButtonSR;
+ }
+
+ public struct JoyConRight
+ {
+ public int StickUp;
+ public int StickDown;
+ public int StickLeft;
+ public int StickRight;
+ public int StickButton;
+ public int ButtonA;
+ public int ButtonB;
+ public int ButtonX;
+ public int ButtonY;
+ public int ButtonPlus;
+ public int ButtonR;
+ public int ButtonZR;
+ public int ButtonSL;
+ public int ButtonSR;
+ }
+
+ public struct JoyCon
+ {
+ public JoyConLeft Left;
+ public JoyConRight Right;
+ }
+}
diff --git a/Ryujinx.Core/Loaders/Compression/Lz4.cs b/Ryujinx.Core/Loaders/Compression/Lz4.cs
new file mode 100644
index 00000000..eb1602a0
--- /dev/null
+++ b/Ryujinx.Core/Loaders/Compression/Lz4.cs
@@ -0,0 +1,78 @@
+using System;
+
+namespace Ryujinx.Core.Loaders.Compression
+{
+ static class Lz4
+ {
+ public static byte[] Decompress(byte[] Cmp, int DecLength)
+ {
+ byte[] Dec = new byte[DecLength];
+
+ int CmpPos = 0;
+ int DecPos = 0;
+
+ int GetLength(int Length)
+ {
+ byte Sum;
+
+ if (Length == 0xf)
+ {
+ do
+ {
+ Length += (Sum = Cmp[CmpPos++]);
+ }
+ while (Sum == 0xff);
+ }
+
+ return Length;
+ }
+
+ do
+ {
+ byte Token = Cmp[CmpPos++];
+
+ int EncCount = (Token >> 0) & 0xf;
+ int LitCount = (Token >> 4) & 0xf;
+
+ //Copy literal chunck
+ LitCount = GetLength(LitCount);
+
+ Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount);
+
+ CmpPos += LitCount;
+ DecPos += LitCount;
+
+ if (CmpPos >= Cmp.Length)
+ {
+ break;
+ }
+
+ //Copy compressed chunck
+ int Back = Cmp[CmpPos++] << 0 |
+ Cmp[CmpPos++] << 8;
+
+ EncCount = GetLength(EncCount) + 4;
+
+ int EncPos = DecPos - Back;
+
+ if (EncCount <= Back)
+ {
+ Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount);
+
+ DecPos += EncCount;
+ }
+ else
+ {
+ while (EncCount-- > 0)
+ {
+ Dec[DecPos++] = Dec[EncPos++];
+ }
+ }
+ }
+ while (CmpPos < Cmp.Length &&
+ DecPos < Dec.Length);
+
+ return Dec;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfDyn.cs b/Ryujinx.Core/Loaders/ElfDyn.cs
new file mode 100644
index 00000000..2ed50b3e
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfDyn.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Core.Loaders
+{
+ struct ElfDyn
+ {
+ public ElfDynTag Tag { get; private set; }
+
+ public long Value { get; private set; }
+
+ public ElfDyn(ElfDynTag Tag, long Value)
+ {
+ this.Tag = Tag;
+ this.Value = Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfDynTag.cs b/Ryujinx.Core/Loaders/ElfDynTag.cs
new file mode 100644
index 00000000..1616c223
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfDynTag.cs
@@ -0,0 +1,72 @@
+namespace Ryujinx.Core.Loaders
+{
+ enum ElfDynTag
+ {
+ DT_NULL = 0,
+ DT_NEEDED = 1,
+ DT_PLTRELSZ = 2,
+ DT_PLTGOT = 3,
+ DT_HASH = 4,
+ DT_STRTAB = 5,
+ DT_SYMTAB = 6,
+ DT_RELA = 7,
+ DT_RELASZ = 8,
+ DT_RELAENT = 9,
+ DT_STRSZ = 10,
+ DT_SYMENT = 11,
+ DT_INIT = 12,
+ DT_FINI = 13,
+ DT_SONAME = 14,
+ DT_RPATH = 15,
+ DT_SYMBOLIC = 16,
+ DT_REL = 17,
+ DT_RELSZ = 18,
+ DT_RELENT = 19,
+ DT_PLTREL = 20,
+ DT_DEBUG = 21,
+ DT_TEXTREL = 22,
+ DT_JMPREL = 23,
+ DT_BIND_NOW = 24,
+ DT_INIT_ARRAY = 25,
+ DT_FINI_ARRAY = 26,
+ DT_INIT_ARRAYSZ = 27,
+ DT_FINI_ARRAYSZ = 28,
+ DT_RUNPATH = 29,
+ DT_FLAGS = 30,
+ DT_ENCODING = 32,
+ DT_PREINIT_ARRAY = 32,
+ DT_PREINIT_ARRAYSZ = 33,
+ DT_GNU_PRELINKED = 0x6ffffdf5,
+ DT_GNU_CONFLICTSZ = 0x6ffffdf6,
+ DT_GNU_LIBLISTSZ = 0x6ffffdf7,
+ DT_CHECKSUM = 0x6ffffdf8,
+ DT_PLTPADSZ = 0x6ffffdf9,
+ DT_MOVEENT = 0x6ffffdfa,
+ DT_MOVESZ = 0x6ffffdfb,
+ DT_FEATURE_1 = 0x6ffffdfc,
+ DT_POSFLAG_1 = 0x6ffffdfd,
+ DT_SYMINSZ = 0x6ffffdfe,
+ DT_SYMINENT = 0x6ffffdff,
+ DT_GNU_HASH = 0x6ffffef5,
+ DT_TLSDESC_PLT = 0x6ffffef6,
+ DT_TLSDESC_GOT = 0x6ffffef7,
+ DT_GNU_CONFLICT = 0x6ffffef8,
+ DT_GNU_LIBLIST = 0x6ffffef9,
+ DT_CONFIG = 0x6ffffefa,
+ DT_DEPAUDIT = 0x6ffffefb,
+ DT_AUDIT = 0x6ffffefc,
+ DT_PLTPAD = 0x6ffffefd,
+ DT_MOVETAB = 0x6ffffefe,
+ DT_SYMINFO = 0x6ffffeff,
+ DT_VERSYM = 0x6ffffff0,
+ DT_RELACOUNT = 0x6ffffff9,
+ DT_RELCOUNT = 0x6ffffffa,
+ DT_FLAGS_1 = 0x6ffffffb,
+ DT_VERDEF = 0x6ffffffc,
+ DT_VERDEFNUM = 0x6ffffffd,
+ DT_VERNEED = 0x6ffffffe,
+ DT_VERNEEDNUM = 0x6fffffff,
+ DT_AUXILIARY = 0x7ffffffd,
+ DT_FILTER = 0x7fffffff
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfRel.cs b/Ryujinx.Core/Loaders/ElfRel.cs
new file mode 100644
index 00000000..8db27452
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfRel.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Core.Loaders
+{
+ struct ElfRel
+ {
+ public long Offset { get; private set; }
+ public long Addend { get; private set; }
+
+ public ElfSym Symbol { get; private set; }
+ public ElfRelType Type { get; private set; }
+
+ public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type)
+ {
+ this.Offset = Offset;
+ this.Addend = Addend;
+ this.Symbol = Symbol;
+ this.Type = Type;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfRelType.cs b/Ryujinx.Core/Loaders/ElfRelType.cs
new file mode 100644
index 00000000..a0533138
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfRelType.cs
@@ -0,0 +1,128 @@
+namespace Ryujinx.Core.Loaders
+{
+ enum ElfRelType
+ {
+ R_AARCH64_NONE = 0,
+ R_AARCH64_ABS64 = 257,
+ R_AARCH64_ABS32 = 258,
+ R_AARCH64_ABS16 = 259,
+ R_AARCH64_PREL64 = 260,
+ R_AARCH64_PREL32 = 261,
+ R_AARCH64_PREL16 = 262,
+ R_AARCH64_MOVW_UABS_G0 = 263,
+ R_AARCH64_MOVW_UABS_G0_NC = 264,
+ R_AARCH64_MOVW_UABS_G1 = 265,
+ R_AARCH64_MOVW_UABS_G1_NC = 266,
+ R_AARCH64_MOVW_UABS_G2 = 267,
+ R_AARCH64_MOVW_UABS_G2_NC = 268,
+ R_AARCH64_MOVW_UABS_G3 = 269,
+ R_AARCH64_MOVW_SABS_G0 = 270,
+ R_AARCH64_MOVW_SABS_G1 = 271,
+ R_AARCH64_MOVW_SABS_G2 = 272,
+ R_AARCH64_LD_PREL_LO19 = 273,
+ R_AARCH64_ADR_PREL_LO21 = 274,
+ R_AARCH64_ADR_PREL_PG_HI21 = 275,
+ R_AARCH64_ADR_PREL_PG_HI21_NC = 276,
+ R_AARCH64_ADD_ABS_LO12_NC = 277,
+ R_AARCH64_LDST8_ABS_LO12_NC = 278,
+ R_AARCH64_TSTBR14 = 279,
+ R_AARCH64_CONDBR19 = 280,
+ R_AARCH64_JUMP26 = 282,
+ R_AARCH64_CALL26 = 283,
+ R_AARCH64_LDST16_ABS_LO12_NC = 284,
+ R_AARCH64_LDST32_ABS_LO12_NC = 285,
+ R_AARCH64_LDST64_ABS_LO12_NC = 286,
+ R_AARCH64_MOVW_PREL_G0 = 287,
+ R_AARCH64_MOVW_PREL_G0_NC = 288,
+ R_AARCH64_MOVW_PREL_G1 = 289,
+ R_AARCH64_MOVW_PREL_G1_NC = 290,
+ R_AARCH64_MOVW_PREL_G2 = 291,
+ R_AARCH64_MOVW_PREL_G2_NC = 292,
+ R_AARCH64_MOVW_PREL_G3 = 293,
+ R_AARCH64_LDST128_ABS_LO12_NC = 299,
+ R_AARCH64_MOVW_GOTOFF_G0 = 300,
+ R_AARCH64_MOVW_GOTOFF_G0_NC = 301,
+ R_AARCH64_MOVW_GOTOFF_G1 = 302,
+ R_AARCH64_MOVW_GOTOFF_G1_NC = 303,
+ R_AARCH64_MOVW_GOTOFF_G2 = 304,
+ R_AARCH64_MOVW_GOTOFF_G2_NC = 305,
+ R_AARCH64_MOVW_GOTOFF_G3 = 306,
+ R_AARCH64_GOTREL64 = 307,
+ R_AARCH64_GOTREL32 = 308,
+ R_AARCH64_GOT_LD_PREL19 = 309,
+ R_AARCH64_LD64_GOTOFF_LO15 = 310,
+ R_AARCH64_ADR_GOT_PAGE = 311,
+ R_AARCH64_LD64_GOT_LO12_NC = 312,
+ R_AARCH64_LD64_GOTPAGE_LO15 = 313,
+ R_AARCH64_TLSGD_ADR_PREL21 = 512,
+ R_AARCH64_TLSGD_ADR_PAGE21 = 513,
+ R_AARCH64_TLSGD_ADD_LO12_NC = 514,
+ R_AARCH64_TLSGD_MOVW_G1 = 515,
+ R_AARCH64_TLSGD_MOVW_G0_NC = 516,
+ R_AARCH64_TLSLD_ADR_PREL21 = 517,
+ R_AARCH64_TLSLD_ADR_PAGE21 = 518,
+ R_AARCH64_TLSLD_ADD_LO12_NC = 519,
+ R_AARCH64_TLSLD_MOVW_G1 = 520,
+ R_AARCH64_TLSLD_MOVW_G0_NC = 521,
+ R_AARCH64_TLSLD_LD_PREL19 = 522,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527,
+ R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528,
+ R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529,
+ R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530,
+ R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531,
+ R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532,
+ R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533,
+ R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534,
+ R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535,
+ R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536,
+ R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537,
+ R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538,
+ R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539,
+ R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540,
+ R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541,
+ R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542,
+ R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543,
+ R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544,
+ R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545,
+ R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546,
+ R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547,
+ R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548,
+ R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549,
+ R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550,
+ R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551,
+ R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552,
+ R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553,
+ R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554,
+ R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555,
+ R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556,
+ R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557,
+ R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558,
+ R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559,
+ R_AARCH64_TLSDESC_LD_PREL19 = 560,
+ R_AARCH64_TLSDESC_ADR_PREL21 = 561,
+ R_AARCH64_TLSDESC_ADR_PAGE21 = 562,
+ R_AARCH64_TLSDESC_LD64_LO12 = 563,
+ R_AARCH64_TLSDESC_ADD_LO12 = 564,
+ R_AARCH64_TLSDESC_OFF_G1 = 565,
+ R_AARCH64_TLSDESC_OFF_G0_NC = 566,
+ R_AARCH64_TLSDESC_LDR = 567,
+ R_AARCH64_TLSDESC_ADD = 568,
+ R_AARCH64_TLSDESC_CALL = 569,
+ R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570,
+ R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571,
+ R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572,
+ R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573,
+ R_AARCH64_COPY = 1024,
+ R_AARCH64_GLOB_DAT = 1025,
+ R_AARCH64_JUMP_SLOT = 1026,
+ R_AARCH64_RELATIVE = 1027,
+ R_AARCH64_TLS_DTPMOD64 = 1028,
+ R_AARCH64_TLS_DTPREL64 = 1029,
+ R_AARCH64_TLS_TPREL64 = 1030,
+ R_AARCH64_TLSDESC = 1031
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfSym.cs b/Ryujinx.Core/Loaders/ElfSym.cs
new file mode 100644
index 00000000..35a45500
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfSym.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.Core.Loaders
+{
+ struct ElfSym
+ {
+ public string Name { get; private set; }
+
+ public ElfSymType Type { get; private set; }
+ public ElfSymBinding Binding { get; private set; }
+ public ElfSymVisibility Visibility { get; private set; }
+
+ public bool IsFuncOrObject =>
+ Type == ElfSymType.STT_FUNC ||
+ Type == ElfSymType.STT_OBJECT;
+
+ public bool IsGlobalOrWeak =>
+ Binding == ElfSymBinding.STB_GLOBAL ||
+ Binding == ElfSymBinding.STB_WEAK;
+
+ public int SHIdx { get; private set; }
+ public long ValueAbs { get; private set; }
+ public long Value { get; private set; }
+ public long Size { get; private set; }
+
+ public ElfSym(
+ string Name,
+ int Info,
+ int Other,
+ int SHIdx,
+ long ImageBase,
+ long Value,
+ long Size)
+ {
+ this.Name = Name;
+ this.Type = (ElfSymType)(Info & 0xf);
+ this.Binding = (ElfSymBinding)(Info >> 4);
+ this.Visibility = (ElfSymVisibility)Other;
+ this.SHIdx = SHIdx;
+ this.ValueAbs = Value + ImageBase;
+ this.Value = Value;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfSymBinding.cs b/Ryujinx.Core/Loaders/ElfSymBinding.cs
new file mode 100644
index 00000000..c8789496
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfSymBinding.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Core.Loaders
+{
+ enum ElfSymBinding
+ {
+ STB_LOCAL = 0,
+ STB_GLOBAL = 1,
+ STB_WEAK = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfSymType.cs b/Ryujinx.Core/Loaders/ElfSymType.cs
new file mode 100644
index 00000000..786395e6
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfSymType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Core.Loaders
+{
+ enum ElfSymType
+ {
+ STT_NOTYPE = 0,
+ STT_OBJECT = 1,
+ STT_FUNC = 2,
+ STT_SECTION = 3,
+ STT_FILE = 4,
+ STT_COMMON = 5,
+ STT_TLS = 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/ElfSymVisibility.cs b/Ryujinx.Core/Loaders/ElfSymVisibility.cs
new file mode 100644
index 00000000..e72eb5b8
--- /dev/null
+++ b/Ryujinx.Core/Loaders/ElfSymVisibility.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.Loaders
+{
+ enum ElfSymVisibility
+ {
+ STV_DEFAULT = 0,
+ STV_INTERNAL = 1,
+ STV_HIDDEN = 2,
+ STV_PROTECTED = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/Executable.cs b/Ryujinx.Core/Loaders/Executable.cs
new file mode 100644
index 00000000..6a6073ef
--- /dev/null
+++ b/Ryujinx.Core/Loaders/Executable.cs
@@ -0,0 +1,149 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Loaders.Executables;
+using Ryujinx.Core.OsHle;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.Loaders
+{
+ class Executable
+ {
+ private AMemory Memory;
+
+ private ElfDyn[] Dynamic;
+
+ public long ImageBase { get; private set; }
+ public long ImageEnd { get; private set; }
+
+ public Executable(IExecutable Exe, AMemory Memory, long ImageBase)
+ {
+ this.Memory = Memory;
+ this.ImageBase = ImageBase;
+ this.ImageEnd = ImageBase;
+
+ WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
+ WriteData(ImageBase + Exe.ROOffset, Exe.RO, MemoryType.Normal, AMemoryPerm.Read);
+ WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.Normal, AMemoryPerm.RW);
+
+ if (Exe.Mod0Offset == 0)
+ {
+ MapBss(ImageBase + Exe.DataOffset + Exe.Data.Count, Exe.BssSize);
+
+ return;
+ }
+
+ long Mod0Offset = ImageBase + Exe.Mod0Offset;
+
+ int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0);
+ long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset;
+ long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset;
+ long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset;
+ long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset;
+ long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset;
+ long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset;
+
+ MapBss(BssStartOffset, BssEndOffset - BssStartOffset);
+
+ ImageEnd = BssEndOffset;
+
+ List<ElfDyn> Dynamic = new List<ElfDyn>();
+
+ while (true)
+ {
+ long TagVal = Memory.ReadInt64(DynamicOffset + 0);
+ long Value = Memory.ReadInt64(DynamicOffset + 8);
+
+ DynamicOffset += 0x10;
+
+ ElfDynTag Tag = (ElfDynTag)TagVal;
+
+ if (Tag == ElfDynTag.DT_NULL)
+ {
+ break;
+ }
+
+ Dynamic.Add(new ElfDyn(Tag, Value));
+ }
+
+ this.Dynamic = Dynamic.ToArray();
+ }
+
+ private void WriteData(
+ long Position,
+ IList<byte> Data,
+ MemoryType Type,
+ AMemoryPerm Perm)
+ {
+ Memory.Manager.MapPhys(Position, Data.Count, (int)Type, AMemoryPerm.Write);
+
+ for (int Index = 0; Index < Data.Count; Index++)
+ {
+ Memory.WriteByte(Position + Index, Data[Index]);
+ }
+
+ Memory.Manager.Reprotect(Position, Data.Count, Perm);
+ }
+
+ private void MapBss(long Position, long Size)
+ {
+ Memory.Manager.MapPhys(Position, Size, (int)MemoryType.Normal, AMemoryPerm.RW);
+ }
+
+ private ElfRel GetRelocation(long Position)
+ {
+ long Offset = Memory.ReadInt64(Position + 0);
+ long Info = Memory.ReadInt64(Position + 8);
+ long Addend = Memory.ReadInt64(Position + 16);
+
+ int RelType = (int)(Info >> 0);
+ int SymIdx = (int)(Info >> 32);
+
+ ElfSym Symbol = GetSymbol(SymIdx);
+
+ return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType);
+ }
+
+ private ElfSym GetSymbol(int Index)
+ {
+ long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB);
+ long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB);
+
+ long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT);
+
+ long Position = SymTblAddr + Index * SymEntSize;
+
+ return GetSymbol(Position, StrTblAddr);
+ }
+
+ private ElfSym GetSymbol(long Position, long StrTblAddr)
+ {
+ int NameIndex = Memory.ReadInt32(Position + 0);
+ int Info = Memory.ReadByte(Position + 4);
+ int Other = Memory.ReadByte(Position + 5);
+ int SHIdx = Memory.ReadInt16(Position + 6);
+ long Value = Memory.ReadInt64(Position + 8);
+ long Size = Memory.ReadInt64(Position + 16);
+
+ string Name = string.Empty;
+
+ for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;)
+ {
+ Name += (char)Chr;
+ }
+
+ return new ElfSym(Name, Info, Other, SHIdx, ImageBase, Value, Size);
+ }
+
+ private long GetFirstValue(ElfDynTag Tag)
+ {
+ foreach (ElfDyn Entry in Dynamic)
+ {
+ if (Entry.Tag == Tag)
+ {
+ return Entry.Value;
+ }
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/Executables/IExecutable.cs b/Ryujinx.Core/Loaders/Executables/IExecutable.cs
new file mode 100644
index 00000000..73787b1d
--- /dev/null
+++ b/Ryujinx.Core/Loaders/Executables/IExecutable.cs
@@ -0,0 +1,17 @@
+using System.Collections.ObjectModel;
+
+namespace Ryujinx.Core.Loaders.Executables
+{
+ public interface IExecutable
+ {
+ ReadOnlyCollection<byte> Text { get; }
+ ReadOnlyCollection<byte> RO { get; }
+ ReadOnlyCollection<byte> Data { get; }
+
+ int Mod0Offset { get; }
+ int TextOffset { get; }
+ int ROOffset { get; }
+ int DataOffset { get; }
+ int BssSize { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/Executables/Nro.cs b/Ryujinx.Core/Loaders/Executables/Nro.cs
new file mode 100644
index 00000000..3cbc4c5d
--- /dev/null
+++ b/Ryujinx.Core/Loaders/Executables/Nro.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace Ryujinx.Core.Loaders.Executables
+{
+ class Nro : IExecutable
+ {
+ private byte[] m_Text;
+ private byte[] m_RO;
+ private byte[] m_Data;
+
+ public ReadOnlyCollection<byte> Text => Array.AsReadOnly(m_Text);
+ public ReadOnlyCollection<byte> RO => Array.AsReadOnly(m_RO);
+ public ReadOnlyCollection<byte> Data => Array.AsReadOnly(m_Data);
+
+ public int Mod0Offset { get; private set; }
+ public int TextOffset { get; private set; }
+ public int ROOffset { get; private set; }
+ public int DataOffset { get; private set; }
+ public int BssSize { get; private set; }
+
+ public Nro(Stream Input)
+ {
+ BinaryReader Reader = new BinaryReader(Input);
+
+ Input.Seek(4, SeekOrigin.Begin);
+
+ int Mod0Offset = Reader.ReadInt32();
+ int Padding8 = Reader.ReadInt32();
+ int Paddingc = Reader.ReadInt32();
+ int NroMagic = Reader.ReadInt32();
+ int Unknown14 = Reader.ReadInt32();
+ int FileSize = Reader.ReadInt32();
+ int Unknown1c = Reader.ReadInt32();
+ int TextOffset = Reader.ReadInt32();
+ int TextSize = Reader.ReadInt32();
+ int ROOffset = Reader.ReadInt32();
+ int ROSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int DataSize = Reader.ReadInt32();
+ int BssSize = Reader.ReadInt32();
+
+ this.Mod0Offset = Mod0Offset;
+ this.TextOffset = TextOffset;
+ this.ROOffset = ROOffset;
+ this.DataOffset = DataOffset;
+ this.BssSize = BssSize;
+
+ byte[] Read(long Position, int Size)
+ {
+ Input.Seek(Position, SeekOrigin.Begin);
+
+ return Reader.ReadBytes(Size);
+ }
+
+ m_Text = Read(TextOffset, TextSize);
+ m_RO = Read(ROOffset, ROSize);
+ m_Data = Read(DataOffset, DataSize);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Loaders/Executables/Nso.cs b/Ryujinx.Core/Loaders/Executables/Nso.cs
new file mode 100644
index 00000000..7b8bf253
--- /dev/null
+++ b/Ryujinx.Core/Loaders/Executables/Nso.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Core.Loaders.Compression;
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace Ryujinx.Core.Loaders.Executables
+{
+ class Nso : IExecutable
+ {
+ private byte[] m_Text;
+ private byte[] m_RO;
+ private byte[] m_Data;
+
+ public ReadOnlyCollection<byte> Text => Array.AsReadOnly(m_Text);
+ public ReadOnlyCollection<byte> RO => Array.AsReadOnly(m_RO);
+ public ReadOnlyCollection<byte> Data => Array.AsReadOnly(m_Data);
+
+ public int Mod0Offset { get; private set; }
+ public int TextOffset { get; private set; }
+ public int ROOffset { get; private set; }
+ public int DataOffset { get; private set; }
+ public int BssSize { get; private set; }
+
+ [Flags]
+ private enum NsoFlags
+ {
+ IsTextCompressed = 1 << 0,
+ IsROCompressed = 1 << 1,
+ IsDataCompressed = 1 << 2,
+ HasTextHash = 1 << 3,
+ HasROHash = 1 << 4,
+ HasDataHash = 1 << 5
+ }
+
+ public Nso(Stream Input)
+ {
+ BinaryReader Reader = new BinaryReader(Input);
+
+ Input.Seek(0, SeekOrigin.Begin);
+
+ int NsoMagic = Reader.ReadInt32();
+ int Version = Reader.ReadInt32();
+ int Reserved = Reader.ReadInt32();
+ int FlagsMsk = Reader.ReadInt32();
+ int TextOffset = Reader.ReadInt32();
+ int TextMemOffset = Reader.ReadInt32();
+ int TextDecSize = Reader.ReadInt32();
+ int ModNameOffset = Reader.ReadInt32();
+ int ROOffset = Reader.ReadInt32();
+ int ROMemOffset = Reader.ReadInt32();
+ int RODecSize = Reader.ReadInt32();
+ int ModNameSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int DataMemOffset = Reader.ReadInt32();
+ int DataDecSize = Reader.ReadInt32();
+ int BssSize = Reader.ReadInt32();
+
+ byte[] BuildId = Reader.ReadBytes(0x20);
+
+ int TextSize = Reader.ReadInt32();
+ int ROSize = Reader.ReadInt32();
+ int DataSize = Reader.ReadInt32();
+
+ Input.Seek(0x24, SeekOrigin.Current);
+
+ int DynStrOffset = Reader.ReadInt32();
+ int DynStrSize = Reader.ReadInt32();
+ int DynSymOffset = Reader.ReadInt32();
+ int DynSymSize = Reader.ReadInt32();
+
+ byte[] TextHash = Reader.ReadBytes(0x20);
+ byte[] ROHash = Reader.ReadBytes(0x20);
+ byte[] DataHash = Reader.ReadBytes(0x20);
+
+ NsoFlags Flags = (NsoFlags)FlagsMsk;
+
+ this.TextOffset = TextMemOffset;
+ this.ROOffset = ROMemOffset;
+ this.DataOffset = DataMemOffset;
+ this.BssSize = BssSize;
+
+ //Text segment
+ Input.Seek(TextOffset, SeekOrigin.Begin);
+
+ m_Text = Reader.ReadBytes(TextSize);
+
+ if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true)
+ {
+ m_Text = Lz4.Decompress(m_Text, TextDecSize);
+ }
+
+ //Read-only data segment
+ Input.Seek(ROOffset, SeekOrigin.Begin);
+
+ m_RO = Reader.ReadBytes(ROSize);
+
+ if (Flags.HasFlag(NsoFlags.IsROCompressed) || true)
+ {
+ m_RO = Lz4.Decompress(m_RO, RODecSize);
+ }
+
+ //Data segment
+ Input.Seek(DataOffset, SeekOrigin.Begin);
+
+ m_Data = Reader.ReadBytes(DataSize);
+
+ if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true)
+ {
+ m_Data = Lz4.Decompress(m_Data, DataDecSize);
+ }
+
+ using (MemoryStream Text = new MemoryStream(m_Text))
+ {
+ BinaryReader TextReader = new BinaryReader(Text);
+
+ Text.Seek(4, SeekOrigin.Begin);
+
+ Mod0Offset = TextReader.ReadInt32();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Logging.cs b/Ryujinx.Core/Logging.cs
new file mode 100644
index 00000000..e14e5587
--- /dev/null
+++ b/Ryujinx.Core/Logging.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+
+namespace Ryujinx.Core
+{
+ public static class Logging
+ {
+ private static Stopwatch ExecutionTime = new Stopwatch();
+ private const string LogFileName = "Ryujinx.log";
+
+ public static bool EnableInfo = Config.LoggingEnableInfo;
+ public static bool EnableTrace = Config.LoggingEnableTrace;
+ public static bool EnableDebug = Config.LoggingEnableDebug;
+ public static bool EnableWarn = Config.LoggingEnableWarn;
+ public static bool EnableError = Config.LoggingEnableError;
+ public static bool EnableFatal = Config.LoggingEnableFatal;
+ public static bool EnableLogFile = Config.LoggingEnableLogFile;
+
+ static Logging()
+ {
+ ExecutionTime.Start();
+
+ if (File.Exists(LogFileName)) File.Delete(LogFileName);
+ }
+
+ public static string GetExecutionTime()
+ {
+ return ExecutionTime.ElapsedMilliseconds.ToString().PadLeft(8, '0') + "ms";
+ }
+
+ private static string WhoCalledMe()
+ {
+ return new StackTrace().GetFrame(2).GetMethod().Name;
+ }
+
+ private static void LogFile(string Message)
+ {
+ if (EnableLogFile)
+ {
+ using (StreamWriter Writer = File.AppendText(LogFileName))
+ {
+ Writer.WriteLine(Message);
+ }
+ }
+ }
+
+ public static void Info(string Message)
+ {
+ if (EnableInfo)
+ {
+ string Text = $"{GetExecutionTime()} | INFO > {Message}";
+
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+
+ public static void Trace(string Message)
+ {
+ if (EnableTrace)
+ {
+ string Text = $"{GetExecutionTime()} | TRACE > {WhoCalledMe()} - {Message}";
+
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+
+ public static void Debug(string Message)
+ {
+ if (EnableDebug)
+ {
+ string Text = $"{GetExecutionTime()} | DEBUG > {WhoCalledMe()} - {Message}";
+
+ Console.ForegroundColor = ConsoleColor.Gray;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+
+ public static void Warn(string Message)
+ {
+ if (EnableWarn)
+ {
+ string Text = $"{GetExecutionTime()} | WARN > {WhoCalledMe()} - {Message}";
+
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+
+ public static void Error(string Message)
+ {
+ if (EnableError)
+ {
+ string Text = $"{GetExecutionTime()} | ERROR > {WhoCalledMe()} - {Message}";
+
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+
+ public static void Fatal(string Message)
+ {
+ if (EnableFatal)
+ {
+ string Text = $"{GetExecutionTime()} | FATAL > {WhoCalledMe()} - {Message}";
+
+ Console.ForegroundColor = ConsoleColor.Magenta;
+ Console.WriteLine(Text.PadLeft(Text.Length + 1, ' '));
+ Console.ResetColor();
+
+ LogFile(Text);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Core/OsHle/CondVar.cs b/Ryujinx.Core/OsHle/CondVar.cs
new file mode 100644
index 00000000..7b3e1852
--- /dev/null
+++ b/Ryujinx.Core/OsHle/CondVar.cs
@@ -0,0 +1,138 @@
+using Ryujinx.Core.OsHle.Handles;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle
+{
+ public class CondVar
+ {
+ private Process Process;
+
+ private long CondVarAddress;
+ private long Timeout;
+
+ private bool OwnsCondVarValue;
+
+ private List<HThread> WaitingThreads;
+
+ public CondVar(Process Process, long CondVarAddress, long Timeout)
+ {
+ this.Process = Process;
+ this.CondVarAddress = CondVarAddress;
+ this.Timeout = Timeout;
+
+ WaitingThreads = new List<HThread>();
+ }
+
+ public void WaitForSignal(HThread Thread)
+ {
+ int Count = Process.Memory.ReadInt32(CondVarAddress);
+
+ if (Count <= 0)
+ {
+ lock (WaitingThreads)
+ {
+ WaitingThreads.Add(Thread);
+ }
+
+ if (Timeout == -1)
+ {
+ Process.Scheduler.WaitForSignal(Thread);
+ }
+ else
+ {
+ Process.Scheduler.WaitForSignal(Thread, (int)(Timeout / 1000000));
+
+ lock (WaitingThreads)
+ {
+ WaitingThreads.Remove(Thread);
+ }
+ }
+ }
+
+ AcquireCondVarValue();
+
+ Count = Process.Memory.ReadInt32(CondVarAddress);
+
+ if (Count > 0)
+ {
+ Process.Memory.WriteInt32(CondVarAddress, Count - 1);
+ }
+
+ ReleaseCondVarValue();
+ }
+
+ public void SetSignal(HThread Thread, int Count)
+ {
+ lock (WaitingThreads)
+ {
+ if (Count == -1)
+ {
+ Process.Scheduler.Signal(WaitingThreads.ToArray());
+
+ AcquireCondVarValue();
+
+ Process.Memory.WriteInt32(CondVarAddress, WaitingThreads.Count);
+
+ ReleaseCondVarValue();
+
+ WaitingThreads.Clear();
+ }
+ else
+ {
+ if (WaitingThreads.Count > 0)
+ {
+ int HighestPriority = WaitingThreads[0].Priority;
+ int HighestPrioIndex = 0;
+
+ for (int Index = 1; Index < WaitingThreads.Count; Index++)
+ {
+ if (HighestPriority > WaitingThreads[Index].Priority)
+ {
+ HighestPriority = WaitingThreads[Index].Priority;
+
+ HighestPrioIndex = Index;
+ }
+ }
+
+ Process.Scheduler.Signal(WaitingThreads[HighestPrioIndex]);
+
+ WaitingThreads.RemoveAt(HighestPrioIndex);
+ }
+
+ AcquireCondVarValue();
+
+ Process.Memory.WriteInt32(CondVarAddress, Count);
+
+ ReleaseCondVarValue();
+ }
+ }
+
+ Process.Scheduler.Suspend(Thread.ProcessorId);
+ Process.Scheduler.Resume(Thread);
+ }
+
+ private void AcquireCondVarValue()
+ {
+ if (!OwnsCondVarValue)
+ {
+ while (!Process.Memory.AcquireAddress(CondVarAddress))
+ {
+ Thread.Yield();
+ }
+
+ OwnsCondVarValue = true;
+ }
+ }
+
+ private void ReleaseCondVarValue()
+ {
+ if (OwnsCondVarValue)
+ {
+ OwnsCondVarValue = false;
+
+ Process.Memory.ReleaseAddress(CondVarAddress);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Display.cs b/Ryujinx.Core/OsHle/Display.cs
new file mode 100644
index 00000000..590841fc
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Display.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle
+{
+ class Display
+ {
+ public string Name { get; private set; }
+
+ public Display(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Exceptions/GuestBrokeExecutionException.cs b/Ryujinx.Core/OsHle/Exceptions/GuestBrokeExecutionException.cs
new file mode 100644
index 00000000..db4929c5
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Exceptions/GuestBrokeExecutionException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.Core.OsHle.Exceptions
+{
+ public class GuestBrokeExecutionException : Exception
+ {
+ private const string ExMsg = "The guest program broke execution!";
+
+ public GuestBrokeExecutionException() : base(ExMsg) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Exceptions/UndefinedInstructionException.cs b/Ryujinx.Core/OsHle/Exceptions/UndefinedInstructionException.cs
new file mode 100644
index 00000000..20cf8386
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Exceptions/UndefinedInstructionException.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Core.OsHle.Exceptions
+{
+ public class UndefinedInstructionException : Exception
+ {
+ private const string ExMsg = "The instruction at 0x{0:x16} (opcode 0x{1:x8}) is undefined!";
+
+ public UndefinedInstructionException() : base() { }
+
+ public UndefinedInstructionException(long Position, int OpCode) : base(string.Format(ExMsg, Position, OpCode)) { }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/FileDesc.cs b/Ryujinx.Core/OsHle/FileDesc.cs
new file mode 100644
index 00000000..4be83bb0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/FileDesc.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle
+{
+ class FileDesc
+ {
+ public string Name { get; private set; }
+
+ public FileDesc(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HDomain.cs b/Ryujinx.Core/OsHle/Handles/HDomain.cs
new file mode 100644
index 00000000..ca287a5f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HDomain.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Core.OsHle.Utilities;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HDomain : HSession
+ {
+ private Dictionary<int, object> Objects;
+
+ private IdPool ObjIds;
+
+ public HDomain(HSession Session) : base(Session)
+ {
+ Objects = new Dictionary<int, object>();
+
+ ObjIds = new IdPool();
+ }
+
+ public int GenerateObjectId(object Obj)
+ {
+ int Id = ObjIds.GenerateId();
+
+ if (Id == -1)
+ {
+ throw new InvalidOperationException();
+ }
+
+ Objects.Add(Id, Obj);
+
+ return Id;
+ }
+
+ public void DeleteObject(int Id)
+ {
+ if (Objects.TryGetValue(Id, out object Obj))
+ {
+ if (Obj is IDisposable DisposableObj)
+ {
+ DisposableObj.Dispose();
+ }
+
+ ObjIds.DeleteId(Id);
+ Objects.Remove(Id);
+ }
+ }
+
+ public object GetObject(int Id)
+ {
+ if (Objects.TryGetValue(Id, out object Obj))
+ {
+ return Obj;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HEvent.cs b/Ryujinx.Core/OsHle/Handles/HEvent.cs
new file mode 100644
index 00000000..4e881ca2
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HEvent.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HEvent
+ {
+
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HNvMap.cs b/Ryujinx.Core/OsHle/Handles/HNvMap.cs
new file mode 100644
index 00000000..09173730
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HNvMap.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HNvMap
+ {
+ public int Id { get; private set; }
+ public int Size { get; private set; }
+
+ public int Align { get; set; }
+ public int Kind { get; set; }
+ public long Address { get; set; }
+
+ public HNvMap(int Id, int Size)
+ {
+ this.Id = Id;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HSession.cs b/Ryujinx.Core/OsHle/Handles/HSession.cs
new file mode 100644
index 00000000..8aa1c06b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HSession.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HSession
+ {
+ public string ServiceName { get; private set; }
+
+ public bool IsInitialized { get; private set; }
+
+ public int State { get; set; }
+
+ public HSession(string ServiceName)
+ {
+ this.ServiceName = ServiceName;
+ }
+
+ public HSession(HSession Session)
+ {
+ ServiceName = Session.ServiceName;
+ IsInitialized = Session.IsInitialized;
+ }
+
+ public void Initialize()
+ {
+ IsInitialized = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HSessionObj.cs b/Ryujinx.Core/OsHle/Handles/HSessionObj.cs
new file mode 100644
index 00000000..ed0530f7
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HSessionObj.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HSessionObj : HSession, IDisposable
+ {
+ public object Obj { get; private set; }
+
+ public HSessionObj(HSession Session, object Obj) : base(Session)
+ {
+ this.Obj = Obj;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing && Obj != null)
+ {
+ if (Obj is IDisposable DisposableObj)
+ {
+ DisposableObj.Dispose();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HSharedMem.cs b/Ryujinx.Core/OsHle/Handles/HSharedMem.cs
new file mode 100644
index 00000000..3d56ff92
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HSharedMem.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HSharedMem
+ {
+ private List<long> Positions;
+
+ public int PositionsCount => Positions.Count;
+
+ public EventHandler<EventArgs> MemoryMapped;
+ public EventHandler<EventArgs> MemoryUnmapped;
+
+ public HSharedMem(long PhysPos)
+ {
+ Positions = new List<long>();
+ }
+
+ public void AddVirtualPosition(long Position)
+ {
+ lock (Positions)
+ {
+ Positions.Add(Position);
+
+ MemoryMapped?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public void RemoveVirtualPosition(long Position)
+ {
+ lock (Positions)
+ {
+ Positions.Remove(Position);
+
+ MemoryUnmapped?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public long GetVirtualPosition(int Index)
+ {
+ lock (Positions)
+ {
+ if (Index < 0 || Index >= Positions.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Positions[Index];
+ }
+ }
+
+ public bool TryGetLastVirtualPosition(out long Position)
+ {
+ lock (Positions)
+ {
+ if (Positions.Count > 0)
+ {
+ Position = Positions[Positions.Count - 1];
+
+ return true;
+ }
+
+ Position = 0;
+
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HThread.cs b/Ryujinx.Core/OsHle/Handles/HThread.cs
new file mode 100644
index 00000000..8bb276fa
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HThread.cs
@@ -0,0 +1,21 @@
+using ChocolArm64;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ public class HThread
+ {
+ public AThread Thread { get; private set; }
+
+ public int ProcessorId { get; private set; }
+ public int Priority { get; private set; }
+
+ public int ThreadId => Thread.ThreadId;
+
+ public HThread(AThread Thread, int ProcessorId, int Priority)
+ {
+ this.Thread = Thread;
+ this.ProcessorId = ProcessorId;
+ this.Priority = Priority;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/HTransferMem.cs b/Ryujinx.Core/OsHle/Handles/HTransferMem.cs
new file mode 100644
index 00000000..701fc451
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/HTransferMem.cs
@@ -0,0 +1,21 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ class HTransferMem
+ {
+ public AMemory Memory { get; private set; }
+ public AMemoryPerm Perm { get; private set; }
+
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size)
+ {
+ this.Memory = Memory;
+ this.Perm = Perm;
+ this.Position = Position;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs
new file mode 100644
index 00000000..2045f8a1
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs
@@ -0,0 +1,334 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Handles
+{
+ public class KProcessScheduler : IDisposable
+ {
+ private class SchedulerThread : IDisposable
+ {
+ public HThread Thread { get; private set; }
+
+ public AutoResetEvent WaitEvent { get; private set; }
+
+ public SchedulerThread(HThread Thread)
+ {
+ this.Thread = Thread;
+
+ WaitEvent = new AutoResetEvent(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ WaitEvent.Dispose();
+ }
+ }
+ }
+
+ private class ThreadQueue
+ {
+ private List<SchedulerThread> Threads;
+
+ public ThreadQueue()
+ {
+ Threads = new List<SchedulerThread>();
+ }
+
+ public void Push(SchedulerThread Thread)
+ {
+ lock (Threads)
+ {
+ Threads.Add(Thread);
+ }
+ }
+
+ public SchedulerThread Pop(int MinPriority = 0x40)
+ {
+ lock (Threads)
+ {
+ SchedulerThread SchedThread;
+
+ int HighestPriority = MinPriority;
+
+ int HighestPrioIndex = -1;
+
+ for (int Index = 0; Index < Threads.Count; Index++)
+ {
+ SchedThread = Threads[Index];
+
+ if (HighestPriority > SchedThread.Thread.Priority)
+ {
+ HighestPriority = SchedThread.Thread.Priority;
+
+ HighestPrioIndex = Index;
+ }
+ }
+
+ if (HighestPrioIndex == -1)
+ {
+ return null;
+ }
+
+ SchedThread = Threads[HighestPrioIndex];
+
+ Threads.RemoveAt(HighestPrioIndex);
+
+ return SchedThread;
+ }
+ }
+
+ public bool HasThread(SchedulerThread SchedThread)
+ {
+ lock (Threads)
+ {
+ return Threads.Contains(SchedThread);
+ }
+ }
+ }
+
+ private ConcurrentDictionary<HThread, SchedulerThread> AllThreads;
+
+ private ThreadQueue[] WaitingToRun;
+
+ private HashSet<int> ActiveProcessors;
+
+ private object SchedLock;
+
+ public KProcessScheduler()
+ {
+ AllThreads = new ConcurrentDictionary<HThread, SchedulerThread>();
+
+ WaitingToRun = new ThreadQueue[4];
+
+ for (int Index = 0; Index < 4; Index++)
+ {
+ WaitingToRun[Index] = new ThreadQueue();
+ }
+
+ ActiveProcessors = new HashSet<int>();
+
+ SchedLock = new object();
+ }
+
+ public void StartThread(HThread Thread)
+ {
+ lock (SchedLock)
+ {
+ SchedulerThread SchedThread = new SchedulerThread(Thread);
+
+ if (!AllThreads.TryAdd(Thread, SchedThread))
+ {
+ return;
+ }
+
+ if (!ActiveProcessors.Contains(Thread.ProcessorId))
+ {
+ ActiveProcessors.Add(Thread.ProcessorId);
+
+ Thread.Thread.Execute();
+
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} running.");
+ }
+ else
+ {
+ WaitingToRun[Thread.ProcessorId].Push(SchedThread);
+
+ Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} waiting to run.");
+ }
+ }
+ }
+
+ public void Suspend(int ProcessorId)
+ {
+ lock (SchedLock)
+ {
+ SchedulerThread SchedThread = WaitingToRun[ProcessorId].Pop();
+
+ if (SchedThread != null)
+ {
+ RunThread(SchedThread);
+ }
+ else
+ {
+ ActiveProcessors.Remove(ProcessorId);
+ }
+ }
+ }
+
+ public void Resume(HThread CurrThread)
+ {
+ SchedulerThread SchedThread;
+
+ Logging.Debug($"{GetDbgThreadInfo(CurrThread)} entering ipc delay wait state.");
+
+ lock (SchedLock)
+ {
+ if (!AllThreads.TryGetValue(CurrThread, out SchedThread))
+ {
+ Logging.Error($"{GetDbgThreadInfo(CurrThread)} was not found on the scheduler queue!");
+
+ return;
+ }
+ }
+
+ TryResumingExecution(SchedThread);
+ }
+
+ public void WaitForSignal(HThread Thread, int Timeout = -1)
+ {
+ SchedulerThread SchedThread;
+
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} entering signal wait state.");
+
+ lock (SchedLock)
+ {
+ SchedThread = WaitingToRun[Thread.ProcessorId].Pop();
+
+ if (SchedThread != null)
+ {
+ RunThread(SchedThread);
+ }
+ else
+ {
+ ActiveProcessors.Remove(Thread.ProcessorId);
+ }
+
+ if (!AllThreads.TryGetValue(Thread, out SchedThread))
+ {
+ Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!");
+
+ return;
+ }
+ }
+
+ if (Timeout >= 0)
+ {
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} has wait timeout of {Timeout}ms.");
+
+ SchedThread.WaitEvent.WaitOne(Timeout);
+ }
+ else
+ {
+ SchedThread.WaitEvent.WaitOne();
+ }
+
+ TryResumingExecution(SchedThread);
+ }
+
+ private void TryResumingExecution(SchedulerThread SchedThread)
+ {
+ HThread Thread = SchedThread.Thread;
+
+ lock (SchedLock)
+ {
+ if (ActiveProcessors.Add(Thread.ProcessorId))
+ {
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
+
+ return;
+ }
+
+ WaitingToRun[Thread.ProcessorId].Push(SchedThread);
+ }
+
+ SchedThread.WaitEvent.WaitOne();
+
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
+ }
+
+ public void Yield(HThread Thread)
+ {
+ SchedulerThread SchedThread;
+
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} yielded execution.");
+
+ lock (SchedLock)
+ {
+ SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.Priority);
+
+ if (SchedThread == null)
+ {
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} resumed because theres nothing better to run.");
+
+ return;
+ }
+
+ RunThread(SchedThread);
+
+ if (!AllThreads.TryGetValue(Thread, out SchedThread))
+ {
+ Logging.Error($"{GetDbgThreadInfo(Thread)} was not found on the scheduler queue!");
+
+ return;
+ }
+
+ WaitingToRun[Thread.ProcessorId].Push(SchedThread);
+ }
+
+ SchedThread.WaitEvent.WaitOne();
+
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} resuming execution...");
+ }
+
+ private void RunThread(SchedulerThread SchedThread)
+ {
+ if (!SchedThread.Thread.Thread.Execute())
+ {
+ SchedThread.WaitEvent.Set();
+ }
+ else
+ {
+ Logging.Debug($"{GetDbgThreadInfo(SchedThread.Thread)} running.");
+ }
+ }
+
+ public void Signal(params HThread[] Threads)
+ {
+ lock (SchedLock)
+ {
+ foreach (HThread Thread in Threads)
+ {
+ if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+ {
+ if (!WaitingToRun[Thread.ProcessorId].HasThread(SchedThread))
+ {
+ Logging.Debug($"{GetDbgThreadInfo(Thread)} signaled.");
+
+ SchedThread.WaitEvent.Set();
+ }
+ }
+ }
+ }
+ }
+
+ private string GetDbgThreadInfo(HThread Thread)
+ {
+ return $"Thread {Thread.ThreadId} (core {Thread.ProcessorId}) prio {Thread.Priority}";
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ foreach (SchedulerThread SchedThread in AllThreads.Values)
+ {
+ SchedThread.Dispose();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Horizon.cs b/Ryujinx.Core/OsHle/Horizon.cs
new file mode 100644
index 00000000..e2d38d52
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Horizon.cs
@@ -0,0 +1,197 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Loaders.Executables;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Utilities;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle
+{
+ public class Horizon
+ {
+ internal const int HidSize = 0x40000;
+ internal const int FontSize = 0x50;
+
+ internal int HidHandle { get; private set; }
+ internal int FontHandle { get; private set; }
+
+ public long HidOffset { get; private set; }
+ public long FontOffset { get; private set; }
+
+ internal IdPool IdGen { get; private set; }
+ internal IdPool NvMapIds { get; private set; }
+
+ internal IdPoolWithObj Handles { get; private set; }
+ internal IdPoolWithObj Fds { get; private set; }
+ internal IdPoolWithObj Displays { get; private set; }
+
+ public ConcurrentDictionary<long, Mutex> Mutexes { get; private set; }
+ public ConcurrentDictionary<long, CondVar> CondVars { get; private set; }
+
+ private ConcurrentDictionary<int, Process> Processes;
+
+ private HSharedMem HidSharedMem;
+
+ private AMemoryAlloc Allocator;
+
+ private Switch Ns;
+
+ public Horizon(Switch Ns)
+ {
+ this.Ns = Ns;
+
+ IdGen = new IdPool();
+ NvMapIds = new IdPool();
+
+ Handles = new IdPoolWithObj();
+ Fds = new IdPoolWithObj();
+ Displays = new IdPoolWithObj();
+
+ Mutexes = new ConcurrentDictionary<long, Mutex>();
+ CondVars = new ConcurrentDictionary<long, CondVar>();
+
+ Processes = new ConcurrentDictionary<int, Process>();
+
+ Allocator = new AMemoryAlloc();
+
+ HidOffset = Allocator.Alloc(HidSize);
+ FontOffset = Allocator.Alloc(FontSize);
+
+ HidSharedMem = new HSharedMem(HidOffset);
+
+ HidSharedMem.MemoryMapped += HidInit;
+
+ HidHandle = Handles.GenerateId(HidSharedMem);
+
+ FontHandle = Handles.GenerateId(new HSharedMem(FontOffset));
+ }
+
+ public void LoadCart(string ExeFsDir, string RomFsFile = null)
+ {
+ if (RomFsFile != null)
+ {
+ Ns.VFs.LoadRomFs(RomFsFile);
+ }
+
+ int ProcessId = IdGen.GenerateId();
+
+ Process MainProcess = new Process(Ns, Allocator, ProcessId);
+
+ void LoadNso(string FileName)
+ {
+ foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
+ {
+ if (Path.GetExtension(File) != string.Empty)
+ {
+ continue;
+ }
+
+ Logging.Info($"Loading {Path.GetFileNameWithoutExtension(File)}...");
+
+ using (FileStream Input = new FileStream(File, FileMode.Open))
+ {
+ Nso Program = new Nso(Input);
+
+ MainProcess.LoadProgram(Program);
+ }
+ }
+ }
+
+ LoadNso("rtld");
+
+ MainProcess.SetEmptyArgs();
+
+ LoadNso("main");
+ LoadNso("subsdk*");
+ LoadNso("sdk");
+
+ MainProcess.InitializeHeap();
+ MainProcess.Run();
+
+ Processes.TryAdd(ProcessId, MainProcess);
+ }
+
+ public void LoadProgram(string FileName)
+ {
+ int ProcessId = IdGen.GenerateId();
+
+ Process MainProcess = new Process(Ns, Allocator, ProcessId);
+
+ using (FileStream Input = new FileStream(FileName, FileMode.Open))
+ {
+ if (Path.GetExtension(FileName).ToLower() == ".nro")
+ {
+ MainProcess.LoadProgram(new Nro(Input));
+ }
+ else
+ {
+ MainProcess.LoadProgram(new Nso(Input));
+ }
+ }
+
+ MainProcess.SetEmptyArgs();
+ MainProcess.InitializeHeap();
+ MainProcess.Run();
+
+ Processes.TryAdd(ProcessId, MainProcess);
+ }
+
+ public void FinalizeAllProcesses()
+ {
+ foreach (Process Process in Processes.Values)
+ {
+ Process.StopAllThreads();
+ Process.Dispose();
+ }
+ }
+
+ internal bool ExitProcess(int ProcessId)
+ {
+ bool Success = Processes.TryRemove(ProcessId, out Process Process);
+
+ if (Success)
+ {
+ Process.StopAllThreads();
+ }
+
+ if (Processes.Count == 0)
+ {
+ Ns.OnFinish(EventArgs.Empty);
+ }
+
+ return Success;
+ }
+
+ internal bool TryGetProcess(int ProcessId, out Process Process)
+ {
+ return Processes.TryGetValue(ProcessId, out Process);
+ }
+
+ internal void CloseHandle(int Handle)
+ {
+ object HndData = Handles.GetData<object>(Handle);
+
+ if (HndData is HTransferMem TransferMem)
+ {
+ TransferMem.Memory.Manager.Reprotect(
+ TransferMem.Position,
+ TransferMem.Size,
+ TransferMem.Perm);
+ }
+
+ Handles.Delete(Handle);
+ }
+
+ private void HidInit(object sender, EventArgs e)
+ {
+ HSharedMem SharedMem = (HSharedMem)sender;
+
+ if (SharedMem.TryGetLastVirtualPosition(out long Position))
+ {
+ Logging.Info($"HID shared memory successfully mapped to {Position:x16}!");
+ Ns.Hid.Init(Position);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcBuffDesc.cs b/Ryujinx.Core/OsHle/Ipc/IpcBuffDesc.cs
new file mode 100644
index 00000000..01bb1539
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcBuffDesc.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ struct IpcBuffDesc
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+ public int Flags { get; private set; }
+
+ public IpcBuffDesc(BinaryReader Reader)
+ {
+ long Word0 = Reader.ReadUInt32();
+ long Word1 = Reader.ReadUInt32();
+ long Word2 = Reader.ReadUInt32();
+
+ Position = Word1;
+ Position |= (Word2 << 4) & 0x0f00000000;
+ Position |= (Word2 << 34) & 0x7000000000;
+
+ Size = Word0;
+ Size |= (Word2 << 8) & 0xf00000000;
+
+ Flags = (int)Word2 & 3;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs b/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs
new file mode 100644
index 00000000..1ef0c408
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcDomCmd.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ enum IpcDomCmd
+ {
+ SendMsg = 1,
+ DeleteObj = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx.Core/OsHle/Ipc/IpcHandleDesc.cs
new file mode 100644
index 00000000..572c0e64
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcHandleDesc.cs
@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ class IpcHandleDesc
+ {
+ public bool HasPId { get; private set; }
+
+ public long PId { get; private set; }
+
+ public int[] ToCopy { get; private set; }
+ public int[] ToMove { get; private set; }
+
+ public IpcHandleDesc(BinaryReader Reader)
+ {
+ int Word = Reader.ReadInt32();
+
+ HasPId = (Word & 1) != 0;
+
+ ToCopy = new int[(Word >> 1) & 0xf];
+ ToMove = new int[(Word >> 5) & 0xf];
+
+ PId = HasPId ? Reader.ReadInt64() : 0;
+
+ for (int Index = 0; Index < ToCopy.Length; Index++)
+ {
+ ToCopy[Index] = Reader.ReadInt32();
+ }
+
+ for (int Index = 0; Index < ToMove.Length; Index++)
+ {
+ ToMove[Index] = Reader.ReadInt32();
+ }
+ }
+
+ public IpcHandleDesc(int[] Copy, int[] Move)
+ {
+ ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy));
+ ToMove = Move ?? throw new ArgumentNullException(nameof(Move));
+ }
+
+ public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move)
+ {
+ this.PId = PId;
+
+ HasPId = true;
+ }
+
+ public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc(
+ new int[] { Handle },
+ new int[0]);
+
+ public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc(
+ new int[0],
+ new int[] { Handle });
+
+ public byte[] GetBytes()
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ int Word = HasPId ? 1 : 0;
+
+ Word |= (ToCopy.Length & 0xf) << 1;
+ Word |= (ToMove.Length & 0xf) << 5;
+
+ Writer.Write(Word);
+
+ if (HasPId)
+ {
+ Writer.Write((long)PId);
+ }
+
+ foreach (int Handle in ToCopy)
+ {
+ Writer.Write(Handle);
+ }
+
+ foreach (int Handle in ToMove)
+ {
+ Writer.Write(Handle);
+ }
+
+ return MS.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs
new file mode 100644
index 00000000..deab8896
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs
@@ -0,0 +1,277 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Objects;
+using Ryujinx.Core.OsHle.Services;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ static class IpcHandler
+ {
+ private static Dictionary<(string, int), ServiceProcessRequest> ServiceCmds =
+ new Dictionary<(string, int), ServiceProcessRequest>()
+ {
+ { ( "acc:u0", 3), Service.AccU0ListOpenUsers },
+ { ( "acc:u0", 5), Service.AccU0GetProfile },
+ { ( "acc:u0", 100), Service.AccU0InitializeApplicationInfo },
+ { ( "acc:u0", 101), Service.AccU0GetBaasAccountManagerForApplication },
+ { ( "apm", 0), Service.ApmOpenSession },
+ { ( "apm:p", 0), Service.ApmOpenSession },
+ { ( "appletOE", 0), Service.AppletOpenApplicationProxy },
+ { ( "audout:u", 0), Service.AudOutListAudioOuts },
+ { ( "audout:u", 1), Service.AudOutOpenAudioOut },
+ { ( "audren:u", 0), Service.AudRenOpenAudioRenderer },
+ { ( "audren:u", 1), Service.AudRenGetAudioRendererWorkBufferSize },
+ { ( "friend:a", 0), Service.FriendCreateFriendService },
+ { ( "fsp-srv", 1), Service.FspSrvInitialize },
+ { ( "fsp-srv", 18), Service.FspSrvMountSdCard },
+ { ( "fsp-srv", 51), Service.FspSrvMountSaveData },
+ { ( "fsp-srv", 200), Service.FspSrvOpenDataStorageByCurrentProcess },
+ { ( "fsp-srv", 203), Service.FspSrvOpenRomStorage },
+ { ( "fsp-srv", 1005), Service.FspSrvGetGlobalAccessLogMode },
+ { ( "hid", 0), Service.HidCreateAppletResource },
+ { ( "hid", 11), Service.HidActivateTouchScreen },
+ { ( "hid", 100), Service.HidSetSupportedNpadStyleSet },
+ { ( "hid", 102), Service.HidSetSupportedNpadIdType },
+ { ( "hid", 103), Service.HidActivateNpad },
+ { ( "hid", 120), Service.HidSetNpadJoyHoldType },
+ { ( "lm", 0), Service.LmInitialize },
+ { ( "nvdrv", 0), Service.NvDrvOpen },
+ { ( "nvdrv", 1), Service.NvDrvIoctl },
+ { ( "nvdrv", 2), Service.NvDrvClose },
+ { ( "nvdrv", 3), Service.NvDrvInitialize },
+ { ( "nvdrv", 4), Service.NvDrvQueryEvent },
+ { ( "nvdrv", 8), Service.NvDrvSetClientPid },
+ { ( "nvdrv:a", 0), Service.NvDrvOpen },
+ { ( "nvdrv:a", 1), Service.NvDrvIoctl },
+ { ( "nvdrv:a", 2), Service.NvDrvClose },
+ { ( "nvdrv:a", 3), Service.NvDrvInitialize },
+ { ( "nvdrv:a", 4), Service.NvDrvQueryEvent },
+ { ( "nvdrv:a", 8), Service.NvDrvSetClientPid },
+ { ( "pctl:a", 0), Service.PctlCreateService },
+ { ( "pl:u", 1), Service.PlGetLoadState },
+ { ( "pl:u", 2), Service.PlGetFontSize },
+ { ( "pl:u", 3), Service.PlGetSharedMemoryAddressOffset },
+ { ( "pl:u", 4), Service.PlGetSharedMemoryNativeHandle },
+ { ( "set", 1), Service.SetGetAvailableLanguageCodes },
+ { ( "sm:", 0), Service.SmInitialize },
+ { ( "sm:", 1), Service.SmGetService },
+ { ( "time:u", 0), Service.TimeGetStandardUserSystemClock },
+ { ( "time:u", 1), Service.TimeGetStandardNetworkSystemClock },
+ { ( "time:u", 2), Service.TimeGetStandardSteadyClock },
+ { ( "time:u", 3), Service.TimeGetTimeZoneService },
+ { ( "time:u", 4), Service.TimeGetStandardLocalSystemClock },
+ { ( "time:s", 0), Service.TimeGetStandardUserSystemClock },
+ { ( "time:s", 1), Service.TimeGetStandardNetworkSystemClock },
+ { ( "time:s", 2), Service.TimeGetStandardSteadyClock },
+ { ( "time:s", 3), Service.TimeGetTimeZoneService },
+ { ( "time:s", 4), Service.TimeGetStandardLocalSystemClock },
+ { ( "vi:m", 2), Service.ViGetDisplayService },
+ };
+
+ private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
+ private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;
+
+ public static void IpcCall(
+ Switch Ns,
+ AMemory Memory,
+ HSession Session,
+ IpcMessage Request,
+ long CmdPtr,
+ int HndId)
+ {
+ IpcMessage Response = new IpcMessage(Request.IsDomain);
+
+ using (MemoryStream Raw = new MemoryStream(Request.RawData))
+ {
+ BinaryReader ReqReader = new BinaryReader(Raw);
+
+ if (Request.Type == IpcMessageType.Request)
+ {
+ string ServiceName = Session.ServiceName;
+
+ ServiceProcessRequest ProcReq = null;
+
+ bool IgnoreNullPR = false;
+
+ string DbgServiceName = string.Empty;
+
+ if (Session is HDomain Dom)
+ {
+ if (Request.DomCmd == IpcDomCmd.SendMsg)
+ {
+ long Magic = ReqReader.ReadInt64();
+ int CmdId = (int)ReqReader.ReadInt64();
+
+ object Obj = Dom.GetObject(Request.DomObjId);
+
+ if (Obj is HDomain)
+ {
+ ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
+
+ DbgServiceName = $"{ServiceName} {ProcReq?.Method.Name ?? CmdId.ToString()}";
+ }
+ else if (Obj != null)
+ {
+ ((IIpcInterface)Obj).Commands.TryGetValue(CmdId, out ProcReq);
+
+ DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}";
+ }
+ }
+ else if (Request.DomCmd == IpcDomCmd.DeleteObj)
+ {
+ Dom.DeleteObject(Request.DomObjId);
+
+ Response = FillResponse(Response, 0);
+
+ IgnoreNullPR = true;
+ }
+ }
+ else
+ {
+ long Magic = ReqReader.ReadInt64();
+ int CmdId = (int)ReqReader.ReadInt64();
+
+ if (Session is HSessionObj)
+ {
+ object Obj = ((HSessionObj)Session).Obj;
+
+ ((IIpcInterface)Obj).Commands.TryGetValue(CmdId, out ProcReq);
+
+ DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {ProcReq?.Method.Name ?? CmdId.ToString()}";
+ }
+ else
+ {
+ ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
+
+ DbgServiceName = $"{ServiceName} {ProcReq?.Method.Name ?? CmdId.ToString()}";
+ }
+ }
+
+ Logging.Debug($"IpcMessage: {DbgServiceName}");
+
+ if (ProcReq != null)
+ {
+ using (MemoryStream ResMS = new MemoryStream())
+ {
+ BinaryWriter ResWriter = new BinaryWriter(ResMS);
+
+ ServiceCtx Context = new ServiceCtx(
+ Ns,
+ Memory,
+ Session,
+ Request,
+ Response,
+ ReqReader,
+ ResWriter);
+
+ long Result = ProcReq(Context);
+
+ Response = FillResponse(Response, Result, ResMS.ToArray());
+ }
+ }
+ else if (!IgnoreNullPR)
+ {
+ throw new NotImplementedException(DbgServiceName);
+ }
+ }
+ else if (Request.Type == IpcMessageType.Control)
+ {
+ long Magic = ReqReader.ReadInt64();
+ long CmdId = ReqReader.ReadInt64();
+
+ switch (CmdId)
+ {
+ case 0: Request = IpcConvertSessionToDomain(Ns, Session, Response, HndId); break;
+ case 3: Request = IpcQueryBufferPointerSize(Response); break;
+ case 4: Request = IpcDuplicateSessionEx(Ns, Session, Response, ReqReader); break;
+
+ default: throw new NotImplementedException(CmdId.ToString());
+ }
+ }
+ else if (Request.Type == IpcMessageType.Unknown2)
+ {
+ //TODO
+ }
+ else
+ {
+ throw new NotImplementedException(Request.Type.ToString());
+ }
+
+ AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr));
+ }
+ }
+
+ private static IpcMessage IpcConvertSessionToDomain(
+ Switch Ns,
+ HSession Session,
+ IpcMessage Response,
+ int HndId)
+ {
+ HDomain Dom = new HDomain(Session);
+
+ Ns.Os.Handles.ReplaceData(HndId, Dom);
+
+ return FillResponse(Response, 0, Dom.GenerateObjectId(Dom));
+ }
+
+ private static IpcMessage IpcDuplicateSessionEx(
+ Switch Ns,
+ HSession Session,
+ IpcMessage Response,
+ BinaryReader ReqReader)
+ {
+ int Unknown = ReqReader.ReadInt32();
+
+ int Handle = Ns.Os.Handles.GenerateId(Session);
+
+ Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+
+ return FillResponse(Response, 0);
+ }
+
+ private static IpcMessage IpcQueryBufferPointerSize(IpcMessage Response)
+ {
+ return FillResponse(Response, 0, 0x500);
+ }
+
+ private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ foreach (int Value in Values)
+ {
+ Writer.Write(Value);
+ }
+
+ return FillResponse(Response, Result, MS.ToArray());
+ }
+ }
+
+ private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null)
+ {
+ Response.Type = IpcMessageType.Response;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ Writer.Write(SfcoMagic);
+ Writer.Write(Result);
+
+ if (Data != null)
+ {
+ Writer.Write(Data);
+ }
+
+ Response.RawData = MS.ToArray();
+ }
+
+ return Response;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
new file mode 100644
index 00000000..3b38c451
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
@@ -0,0 +1,231 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ class IpcMessage
+ {
+ public IpcMessageType Type { get; set; }
+
+ public IpcHandleDesc HandleDesc { get; set; }
+
+ public List<IpcPtrBuffDesc> PtrBuff { get; private set; }
+ public List<IpcBuffDesc> SendBuff { get; private set; }
+ public List<IpcBuffDesc> ReceiveBuff { get; private set; }
+ public List<IpcBuffDesc> ExchangeBuff { get; private set; }
+ public List<IpcRecvListBuffDesc> RecvListBuff { get; private set; }
+
+ public List<int> ResponseObjIds { get; private set; }
+
+ public bool IsDomain { get; private set; }
+ public IpcDomCmd DomCmd { get; private set; }
+ public int DomObjId { get; private set; }
+
+ public byte[] RawData { get; set; }
+
+ public IpcMessage()
+ {
+ PtrBuff = new List<IpcPtrBuffDesc>();
+ SendBuff = new List<IpcBuffDesc>();
+ ReceiveBuff = new List<IpcBuffDesc>();
+ ExchangeBuff = new List<IpcBuffDesc>();
+ RecvListBuff = new List<IpcRecvListBuffDesc>();
+
+ ResponseObjIds = new List<int>();
+ }
+
+ public IpcMessage(bool Domain) : this()
+ {
+ IsDomain = Domain;
+ }
+
+ public IpcMessage(byte[] Data, long CmdPtr, bool Domain) : this()
+ {
+ using (MemoryStream MS = new MemoryStream(Data))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ Initialize(Reader, CmdPtr, Domain);
+ }
+ }
+
+ private void Initialize(BinaryReader Reader, long CmdPtr, bool Domain)
+ {
+ IsDomain = Domain;
+
+ int Word0 = Reader.ReadInt32();
+ int Word1 = Reader.ReadInt32();
+
+ Type = (IpcMessageType)(Word0 & 0xffff);
+
+ int PtrBuffCount = (Word0 >> 16) & 0xf;
+ int SendBuffCount = (Word0 >> 20) & 0xf;
+ int RecvBuffCount = (Word0 >> 24) & 0xf;
+ int XchgBuffCount = (Word0 >> 28) & 0xf;
+
+ int RawDataSize = (Word1 >> 0) & 0x3ff;
+ int RecvListFlags = (Word1 >> 10) & 0xf;
+ bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0;
+
+ if (HndDescEnable)
+ {
+ HandleDesc = new IpcHandleDesc(Reader);
+ }
+
+ for (int Index = 0; Index < PtrBuffCount; Index++)
+ {
+ PtrBuff.Add(new IpcPtrBuffDesc(Reader));
+ }
+
+ void ReadBuff(List<IpcBuffDesc> Buff, int Count)
+ {
+ for (int Index = 0; Index < Count; Index++)
+ {
+ Buff.Add(new IpcBuffDesc(Reader));
+ }
+ }
+
+ ReadBuff(SendBuff, SendBuffCount);
+ ReadBuff(ReceiveBuff, RecvBuffCount);
+ ReadBuff(ExchangeBuff, XchgBuffCount);
+
+ RawDataSize *= 4;
+
+ long RecvListPos = Reader.BaseStream.Position + RawDataSize;
+
+ long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr);
+
+ Reader.BaseStream.Seek(Pad0, SeekOrigin.Current);
+
+ int RecvListCount = RecvListFlags - 2;
+
+ if (RecvListCount == 0)
+ {
+ RecvListCount = 1;
+ }
+ else if (RecvListCount < 0)
+ {
+ RecvListCount = 0;
+ }
+
+ if (Domain)
+ {
+ int DomWord0 = Reader.ReadInt32();
+
+ DomCmd = (IpcDomCmd)(DomWord0 & 0xff);
+
+ RawDataSize = (DomWord0 >> 16) & 0xffff;
+
+ DomObjId = Reader.ReadInt32();
+
+ Reader.ReadInt64(); //Padding
+ }
+
+ RawData = Reader.ReadBytes(RawDataSize);
+
+ Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin);
+
+ for (int Index = 0; Index < RecvListCount; Index++)
+ {
+ RecvListBuff.Add(new IpcRecvListBuffDesc(Reader));
+ }
+ }
+
+ public byte[] GetBytes(long CmdPtr)
+ {
+ //todo
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ int Word0;
+ int Word1;
+
+ Word0 = (int)Type;
+ Word0 |= (PtrBuff.Count & 0xf) << 16;
+ Word0 |= (SendBuff.Count & 0xf) << 20;
+ Word0 |= (ReceiveBuff.Count & 0xf) << 24;
+ Word0 |= (ExchangeBuff.Count & 0xf) << 28;
+
+ byte[] HandleData = new byte[0];
+
+ if (HandleDesc != null)
+ {
+ HandleData = HandleDesc.GetBytes();
+ }
+
+ int DataLength = RawData?.Length ?? 0;
+
+ int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length);
+
+ //Apparently, padding after Raw Data is 16 bytes, however when there is
+ //padding before Raw Data too, we need to subtract the size of this padding.
+ //This is the weirdest padding I've seen so far...
+ int Pad1 = 0x10 - Pad0;
+
+ DataLength = (DataLength + Pad0 + Pad1 + (IsDomain ? 0x10 : 0)) / 4;
+
+ DataLength += ResponseObjIds.Count;
+
+ Word1 = DataLength & 0x3ff;
+
+ if (HandleDesc != null)
+ {
+ Word1 |= 1 << 31;
+ }
+
+ Writer.Write(Word0);
+ Writer.Write(Word1);
+ Writer.Write(HandleData);
+
+ MS.Seek(Pad0, SeekOrigin.Current);
+
+ if (IsDomain)
+ {
+ Writer.Write(ResponseObjIds.Count);
+ Writer.Write(0);
+ Writer.Write(0L);
+ }
+
+ if (RawData != null)
+ {
+ Writer.Write(RawData);
+ }
+
+ foreach (int Id in ResponseObjIds)
+ {
+ Writer.Write(Id);
+ }
+
+ Writer.Write(new byte[Pad1]);
+
+ return MS.ToArray();
+ }
+ }
+
+ private long GetPadSize16(long Position)
+ {
+ if ((Position & 0xf) != 0)
+ {
+ return 0x10 - (Position & 0xf);
+ }
+
+ return 0;
+ }
+
+ public long GetSendBuffPtr()
+ {
+ if (SendBuff.Count > 0 && SendBuff[0].Position != 0)
+ {
+ return SendBuff[0].Position;
+ }
+
+ if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0)
+ {
+ return PtrBuff[0].Position;
+ }
+
+ return -1;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMessageType.cs b/Ryujinx.Core/OsHle/Ipc/IpcMessageType.cs
new file mode 100644
index 00000000..8027508d
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcMessageType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ enum IpcMessageType
+ {
+ Response = 0,
+ Unknown2 = 2,
+ Request = 4,
+ Control = 5
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs
new file mode 100644
index 00000000..d39f78db
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcPtrBuffDesc.cs
@@ -0,0 +1,26 @@
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ struct IpcPtrBuffDesc
+ {
+ public long Position { get; private set; }
+ public int Index { get; private set; }
+ public short Size { get; private set; }
+
+ public IpcPtrBuffDesc(BinaryReader Reader)
+ {
+ long Word0 = Reader.ReadUInt32();
+ long Word1 = Reader.ReadUInt32();
+
+ Position = Word1;
+ Position |= (Word0 << 20) & 0x0f00000000;
+ Position |= (Word0 << 30) & 0x7000000000;
+
+ Index = ((int)Word0 >> 0) & 0x03f;
+ Index |= ((int)Word0 >> 3) & 0x1c0;
+
+ Size = (short)(Word0 >> 16);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.Core/OsHle/Ipc/IpcRecvListBuffDesc.cs
new file mode 100644
index 00000000..c647208f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/IpcRecvListBuffDesc.cs
@@ -0,0 +1,19 @@
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ struct IpcRecvListBuffDesc
+ {
+ public long Position { get; private set; }
+ public short Size { get; private set; }
+
+ public IpcRecvListBuffDesc(BinaryReader Reader)
+ {
+ long Value = Reader.ReadInt64();
+
+ Position = Value & 0xffffffffffff;
+
+ Size = (short)(Value >> 48);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Ipc/ServiceProcessRequest.cs b/Ryujinx.Core/OsHle/Ipc/ServiceProcessRequest.cs
new file mode 100644
index 00000000..ea758074
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Ipc/ServiceProcessRequest.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Core.OsHle.Ipc
+{
+ delegate long ServiceProcessRequest(ServiceCtx Context);
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/MemoryInfo.cs b/Ryujinx.Core/OsHle/MemoryInfo.cs
new file mode 100644
index 00000000..76a4bef3
--- /dev/null
+++ b/Ryujinx.Core/OsHle/MemoryInfo.cs
@@ -0,0 +1,28 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Core.OsHle
+{
+ struct MemoryInfo
+ {
+ public long BaseAddress;
+ public long Size;
+ public int MemType;
+ public int MemAttr;
+ public int MemPerm;
+ public int IpcRefCount;
+ public int DeviceRefCount;
+ public int Padding; //SBZ
+
+ public MemoryInfo(AMemoryMapInfo MapInfo)
+ {
+ BaseAddress = MapInfo.Position;
+ Size = MapInfo.Size;
+ MemType = MapInfo.Type;
+ MemAttr = MapInfo.Attr;
+ MemPerm = (int)MapInfo.Perm;
+ IpcRefCount = 0;
+ DeviceRefCount = 0;
+ Padding = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/MemoryType.cs b/Ryujinx.Core/OsHle/MemoryType.cs
new file mode 100644
index 00000000..d3b43dd5
--- /dev/null
+++ b/Ryujinx.Core/OsHle/MemoryType.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Core.OsHle
+{
+ enum MemoryType
+ {
+ Unmapped = 0,
+ Io = 1,
+ Normal = 2,
+ CodeStatic = 3,
+ CodeMutable = 4,
+ Heap = 5,
+ SharedMemory = 6,
+ ModCodeStatic = 8,
+ ModCodeMutable = 9,
+ IpcBuffer0 = 10,
+ MappedMemory = 11,
+ ThreadLocal = 12,
+ TransferMemoryIsolated = 13,
+ TransferMemory = 14,
+ ProcessMemory = 15,
+ Reserved = 16,
+ IpcBuffer1 = 17,
+ IpcBuffer3 = 18,
+ KernelStack = 19
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Mutex.cs b/Ryujinx.Core/OsHle/Mutex.cs
new file mode 100644
index 00000000..c95ed771
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Mutex.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Core.OsHle.Handles;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle
+{
+ public class Mutex
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private Process Process;
+
+ private long MutexAddress;
+
+ private bool OwnsMutexValue;
+
+ private object EnterWaitLock;
+
+ private ConcurrentQueue<HThread> WaitingThreads;
+
+ public Mutex(Process Process, long MutexAddress, int OwnerThreadHandle)
+ {
+ this.Process = Process;
+ this.MutexAddress = MutexAddress;
+
+ //Process.Memory.WriteInt32(MutexAddress, OwnerThreadHandle);
+
+ EnterWaitLock = new object();
+
+ WaitingThreads = new ConcurrentQueue<HThread>();
+ }
+
+ public void WaitForLock(HThread RequestingThread, int RequestingThreadHandle)
+ {
+ AcquireMutexValue();
+
+ lock (EnterWaitLock)
+ {
+ int CurrentThreadHandle = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask;
+
+ if (CurrentThreadHandle == RequestingThreadHandle ||
+ CurrentThreadHandle == 0)
+ {
+ return;
+ }
+
+ Process.Memory.WriteInt32(MutexAddress, CurrentThreadHandle | MutexHasListenersMask);
+
+ ReleaseMutexValue();
+
+ WaitingThreads.Enqueue(RequestingThread);
+ }
+
+ Process.Scheduler.WaitForSignal(RequestingThread);
+ }
+
+ public void GiveUpLock(int ThreadHandle)
+ {
+ AcquireMutexValue();
+
+ lock (EnterWaitLock)
+ {
+ int CurrentThread = Process.Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask;
+
+ if (CurrentThread == ThreadHandle)
+ {
+ Unlock();
+ }
+ }
+
+ ReleaseMutexValue();
+ }
+
+ public void Unlock()
+ {
+ AcquireMutexValue();
+
+ lock (EnterWaitLock)
+ {
+ int HasListeners = WaitingThreads.Count > 1 ? MutexHasListenersMask : 0;
+
+ Process.Memory.WriteInt32(MutexAddress, HasListeners);
+
+ ReleaseMutexValue();
+
+ HThread[] UnlockedThreads = new HThread[WaitingThreads.Count];
+
+ int Index = 0;
+
+ while (WaitingThreads.TryDequeue(out HThread Thread))
+ {
+ UnlockedThreads[Index++] = Thread;
+ }
+
+ Process.Scheduler.Signal(UnlockedThreads);
+ }
+ }
+
+ private void AcquireMutexValue()
+ {
+ if (!OwnsMutexValue)
+ {
+ while (!Process.Memory.AcquireAddress(MutexAddress))
+ {
+ Thread.Yield();
+ }
+
+ OwnsMutexValue = true;
+ }
+ }
+
+ private void ReleaseMutexValue()
+ {
+ if (OwnsMutexValue)
+ {
+ OwnsMutexValue = false;
+
+ Process.Memory.ReleaseAddress(MutexAddress);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Acc/IManagerForApplication.cs b/Ryujinx.Core/OsHle/Objects/Acc/IManagerForApplication.cs
new file mode 100644
index 00000000..afbfab24
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Acc/IManagerForApplication.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Acc
+{
+ class IManagerForApplication : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManagerForApplication()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CheckAvailability },
+ { 1, GetAccountId }
+ };
+ }
+
+ public long CheckAvailability(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long GetAccountId(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0xcafeL);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Acc/IProfile.cs b/Ryujinx.Core/OsHle/Objects/Acc/IProfile.cs
new file mode 100644
index 00000000..94d17183
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Acc/IProfile.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Acc
+{
+ class IProfile : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IProfile()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, GetBase }
+ };
+ }
+
+ public long GetBase(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IApplicationFunctions.cs b/Ryujinx.Core/OsHle/Objects/Am/IApplicationFunctions.cs
new file mode 100644
index 00000000..939ad248
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IApplicationFunctions.cs
@@ -0,0 +1,80 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IApplicationFunctions : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationFunctions()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, PopLaunchParameter },
+ { 20, EnsureSaveData },
+ { 21, GetDesiredLanguage },
+ { 40, NotifyRunning }
+ };
+ }
+
+ private const uint LaunchParamsMagic = 0xc79497ca;
+
+ public long PopLaunchParameter(ServiceCtx Context)
+ {
+ //Only the first 0x18 bytes of the Data seems to be actually used.
+ MakeObject(Context, new IStorage(MakeLaunchParams()));
+
+ return 0;
+ }
+
+ public long EnsureSaveData(ServiceCtx Context)
+ {
+ long UIdLow = Context.RequestData.ReadInt64();
+ long UIdHigh = Context.RequestData.ReadInt64();
+
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long GetDesiredLanguage(ServiceCtx Context)
+ {
+ //This is an enumerator where each number is a differnet language.
+ //0 is Japanese and 1 is English, need to figure out the other codes.
+ Context.ResponseData.Write(1L);
+
+ return 0;
+ }
+
+ public long NotifyRunning(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1);
+
+ return 0;
+ }
+
+ private byte[] MakeLaunchParams()
+ {
+ //Size needs to be at least 0x88 bytes otherwise application errors.
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ MS.SetLength(0x88);
+
+ Writer.Write(LaunchParamsMagic);
+ Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used.
+ Writer.Write(1L); //User Id Low (note: User Id needs to be != 0)
+ Writer.Write(0L); //User Id High
+
+ return MS.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IApplicationProxy.cs b/Ryujinx.Core/OsHle/Objects/Am/IApplicationProxy.cs
new file mode 100644
index 00000000..4a164daf
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IApplicationProxy.cs
@@ -0,0 +1,85 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IApplicationProxy : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationProxy()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCommonStateGetter },
+ { 1, GetSelfController },
+ { 2, GetWindowController },
+ { 3, GetAudioController },
+ { 4, GetDisplayController },
+ { 11, GetLibraryAppletCreator },
+ { 20, GetApplicationFunctions },
+ { 1000, GetDebugFunctions }
+ };
+ }
+
+ public long GetCommonStateGetter(ServiceCtx Context)
+ {
+ MakeObject(Context, new ICommonStateGetter());
+
+ return 0;
+ }
+
+ public long GetSelfController(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISelfController());
+
+ return 0;
+ }
+
+ public long GetWindowController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IWindowController());
+
+ return 0;
+ }
+
+ public long GetAudioController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAudioController());
+
+ return 0;
+ }
+
+ public long GetDisplayController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDisplayController());
+
+ return 0;
+ }
+
+ public long GetLibraryAppletCreator(ServiceCtx Context)
+ {
+ MakeObject(Context, new ILibraryAppletCreator());
+
+ return 0;
+ }
+
+ public long GetApplicationFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IApplicationFunctions());
+
+ return 0;
+ }
+
+ public long GetDebugFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDebugFunctions());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IAudioController.cs b/Ryujinx.Core/OsHle/Objects/Am/IAudioController.cs
new file mode 100644
index 00000000..c37042fd
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IAudioController.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IAudioController : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/ICommonStateGetter.cs b/Ryujinx.Core/OsHle/Objects/Am/ICommonStateGetter.cs
new file mode 100644
index 00000000..83d61fa6
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/ICommonStateGetter.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class ICommonStateGetter : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ICommonStateGetter()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetEventHandle },
+ { 1, ReceiveMessage },
+ { 5, GetOperationMode },
+ { 6, GetPerformanceMode },
+ { 9, GetCurrentFocusState },
+ };
+ }
+
+ private enum FocusState
+ {
+ InFocus = 1,
+ OutOfFocus = 2
+ }
+
+ private enum OperationMode
+ {
+ Handheld = 0,
+ Docked = 1
+ }
+
+ public long GetEventHandle(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long ReceiveMessage(ServiceCtx Context)
+ {
+ //Program expects 0xF at 0x17ae70 on puyo sdk,
+ //otherwise runs on a infinite loop until it reads said value.
+ //What it means is still unknown.
+ Context.ResponseData.Write(0xfL);
+
+ return 0; //0x680;
+ }
+
+ public long GetOperationMode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((byte)OperationMode.Handheld);
+
+ return 0;
+ }
+
+ public long GetPerformanceMode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((byte)0);
+
+ return 0;
+ }
+
+ public long GetCurrentFocusState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((byte)FocusState.InFocus);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IDebugFunctions.cs b/Ryujinx.Core/OsHle/Objects/Am/IDebugFunctions.cs
new file mode 100644
index 00000000..d04d8363
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IDebugFunctions.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IDebugFunctions : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IDebugFunctions()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IDisplayController.cs b/Ryujinx.Core/OsHle/Objects/Am/IDisplayController.cs
new file mode 100644
index 00000000..9eafa70d
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IDisplayController.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IDisplayController : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IDisplayController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/ILibraryAppletCreator.cs b/Ryujinx.Core/OsHle/Objects/Am/ILibraryAppletCreator.cs
new file mode 100644
index 00000000..10e0f4f4
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/ILibraryAppletCreator.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class ILibraryAppletCreator : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ILibraryAppletCreator()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IParentalControlService.cs b/Ryujinx.Core/OsHle/Objects/Am/IParentalControlService.cs
new file mode 100644
index 00000000..1feacfa6
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IParentalControlService.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IParentalControlService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IParentalControlService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/ISelfController.cs b/Ryujinx.Core/OsHle/Objects/Am/ISelfController.cs
new file mode 100644
index 00000000..691bb202
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/ISelfController.cs
@@ -0,0 +1,53 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class ISelfController : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISelfController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 11, SetOperationModeChangedNotification },
+ { 12, SetPerformanceModeChangedNotification },
+ { 13, SetFocusHandlingMode },
+ { 16, SetOutOfFocusSuspendingEnabled }
+ };
+ }
+
+ public long SetOperationModeChangedNotification(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ return 0;
+ }
+
+ public long SetPerformanceModeChangedNotification(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ return 0;
+ }
+
+ public long SetFocusHandlingMode(ServiceCtx Context)
+ {
+ bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false;
+ bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false;
+ bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ return 0;
+ }
+
+ public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IStorage.cs b/Ryujinx.Core/OsHle/Objects/Am/IStorage.cs
new file mode 100644
index 00000000..b30059ba
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IStorage.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IStorage : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public byte[] Data { get; private set; }
+
+ public IStorage(byte[] Data)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Open }
+ };
+
+ this.Data = Data;
+ }
+
+ public long Open(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorageAccessor(this));
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IStorageAccessor.cs b/Ryujinx.Core/OsHle/Objects/Am/IStorageAccessor.cs
new file mode 100644
index 00000000..df260cc3
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IStorageAccessor.cs
@@ -0,0 +1,62 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IStorageAccessor : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private IStorage Storage;
+
+ public IStorageAccessor(IStorage Storage)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetSize },
+ { 11, Read }
+ };
+
+ this.Storage = Storage;
+ }
+
+ public long GetSize(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)Storage.Data.Length);
+
+ return 0;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long ReadPosition = Context.RequestData.ReadInt64();
+
+ if (Context.Request.RecvListBuff.Count > 0)
+ {
+ long Position = Context.Request.RecvListBuff[0].Position;
+ short Size = Context.Request.RecvListBuff[0].Size;
+
+ byte[] Data;
+
+ if (Storage.Data.Length > Size)
+ {
+ Data = new byte[Size];
+
+ Buffer.BlockCopy(Storage.Data, 0, Data, 0, Size);
+ }
+ else
+ {
+ Data = Storage.Data;
+ }
+
+ AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Am/IWindowController.cs b/Ryujinx.Core/OsHle/Objects/Am/IWindowController.cs
new file mode 100644
index 00000000..aa6e961e
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Am/IWindowController.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Am
+{
+ class IWindowController : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IWindowController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, GetAppletResourceUserId },
+ { 10, AcquireForegroundRights }
+ };
+ }
+
+ public long GetAppletResourceUserId(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long AcquireForegroundRights(ServiceCtx Context)
+ {
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Apm/ISession.cs b/Ryujinx.Core/OsHle/Objects/Apm/ISession.cs
new file mode 100644
index 00000000..3ab33005
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Apm/ISession.cs
@@ -0,0 +1,28 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Apm
+{
+ class ISession : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISession()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, SetPerformanceConfiguration }
+ };
+ }
+
+ public long SetPerformanceConfiguration(ServiceCtx Context)
+ {
+ int PerfMode = Context.RequestData.ReadInt32();
+ int PerfConfig = Context.RequestData.ReadInt32();
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Aud/IAudioOut.cs b/Ryujinx.Core/OsHle/Objects/Aud/IAudioOut.cs
new file mode 100644
index 00000000..061c6376
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Aud/IAudioOut.cs
@@ -0,0 +1,180 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using OpenTK.Audio;
+using OpenTK.Audio.OpenAL;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Objects.Aud
+{
+ class IAudioOut : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioOut()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetAudioOutState },
+ { 1, StartAudioOut },
+ { 2, StopAudioOut },
+ { 3, AppendAudioOutBuffer },
+ { 4, RegisterBufferEvent },
+ { 5, GetReleasedAudioOutBuffer },
+ { 6, ContainsAudioOutBuffer },
+ { 7, AppendAudioOutBuffer_ex },
+ { 8, GetReleasedAudioOutBuffer_ex }
+ };
+ }
+
+ enum AudioOutState
+ {
+ Started,
+ Stopped
+ };
+
+ //IAudioOut
+ private AudioOutState State = AudioOutState.Stopped;
+ private Queue<long> KeysQueue = new Queue<long>();
+
+ //OpenAL
+ private bool OpenALInstalled = true;
+ private AudioContext AudioCtx;
+ private int Source;
+ private int Buffer;
+
+ //Return State of IAudioOut
+ public long GetAudioOutState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)State);
+
+ return 0;
+ }
+
+ public long StartAudioOut(ServiceCtx Context)
+ {
+ if (State == AudioOutState.Stopped)
+ {
+ State = AudioOutState.Started;
+
+ try
+ {
+ AudioCtx = new AudioContext(); //Create the audio context
+ }
+ catch (Exception)
+ {
+ Logging.Warn("OpenAL Error! PS: Install OpenAL Core SDK!");
+ OpenALInstalled = false;
+ }
+
+ if (OpenALInstalled) AL.Listener(ALListenerf.Gain, (float)8.0); //Add more gain to it
+ }
+
+ return 0;
+ }
+
+ public long StopAudioOut(ServiceCtx Context)
+ {
+ if (State == AudioOutState.Started)
+ {
+ if (OpenALInstalled)
+ {
+ if (AudioCtx == null) //Needed to call the instance of AudioContext()
+ return 0;
+
+ AL.SourceStop(Source);
+ AL.DeleteSource(Source);
+ }
+ State = AudioOutState.Stopped;
+ }
+
+ return 0;
+ }
+
+ public long AppendAudioOutBuffer(ServiceCtx Context)
+ {
+ long BufferId = Context.RequestData.ReadInt64();
+
+ KeysQueue.Enqueue(BufferId);
+
+ byte[] AudioOutBuffer = AMemoryHelper.ReadBytes(Context.Memory, Context.Request.SendBuff[0].Position, sizeof(long) * 5);
+ using (MemoryStream MS = new MemoryStream(AudioOutBuffer))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+ long PointerNextBuffer = Reader.ReadInt64();
+ long PointerSampleBuffer = Reader.ReadInt64();
+ long CapacitySampleBuffer = Reader.ReadInt64();
+ long SizeDataInSampleBuffer = Reader.ReadInt64();
+ long OffsetDataInSampleBuffer = Reader.ReadInt64();
+
+ byte[] AudioSampleBuffer = AMemoryHelper.ReadBytes(Context.Memory, PointerSampleBuffer + OffsetDataInSampleBuffer, (int)SizeDataInSampleBuffer);
+
+ if (OpenALInstalled)
+ {
+ if (AudioCtx == null) //Needed to call the instance of AudioContext()
+ return 0;
+
+ Buffer = AL.GenBuffer();
+ AL.BufferData(Buffer, ALFormat.Stereo16, AudioSampleBuffer, AudioSampleBuffer.Length, 48000);
+
+ Source = AL.GenSource();
+ AL.SourceQueueBuffer(Source, Buffer);
+ }
+ }
+
+ return 0;
+ }
+
+ public long RegisterBufferEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long GetReleasedAudioOutBuffer(ServiceCtx Context)
+ {
+ long TempKey = 0;
+
+ if (KeysQueue.Count > 0) TempKey = KeysQueue.Dequeue();
+
+ AMemoryHelper.WriteBytes(Context.Memory, Context.Request.ReceiveBuff[0].Position, BitConverter.GetBytes(TempKey));
+
+ int ReleasedBuffersCount = 1;
+ Context.ResponseData.Write(ReleasedBuffersCount);
+
+ if (OpenALInstalled)
+ {
+ if (AudioCtx == null) //Needed to call the instance of AudioContext()
+ return 0;
+
+ AL.SourcePlay(Source);
+ int[] FreeBuffers = AL.SourceUnqueueBuffers(Source, 1);
+ AL.DeleteBuffers(FreeBuffers);
+ }
+
+ return 0;
+ }
+
+ public long ContainsAudioOutBuffer(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long AppendAudioOutBuffer_ex(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long GetReleasedAudioOutBuffer_ex(ServiceCtx Context)
+ {
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.Core/OsHle/Objects/Aud/IAudioRenderer.cs b/Ryujinx.Core/OsHle/Objects/Aud/IAudioRenderer.cs
new file mode 100644
index 00000000..05356477
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Aud/IAudioRenderer.cs
@@ -0,0 +1,66 @@
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Aud
+{
+ class IAudioRenderer : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioRenderer()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, RequestUpdateAudioRenderer },
+ { 5, StartAudioRenderer },
+ { 6, StopAudioRenderer },
+ { 7, QuerySystemEvent }
+ };
+ }
+
+ public long RequestUpdateAudioRenderer(ServiceCtx Context)
+ {
+ //(buffer<unknown, 5, 0>) -> (buffer<unknown, 6, 0>, buffer<unknown, 6, 0>)
+
+ long Position = Context.Request.ReceiveBuff[0].Position;
+
+ //0x40 bytes header
+ Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section)
+ Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size?
+ Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size?
+ Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size?
+ Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size?
+ Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size?
+ Context.Memory.WriteInt32(Position + 0x3c, 0x20e0); //Total Size (including 0x40 bytes header)
+
+ for (int Offset = 0x40; Offset < 0x40 + 0x18e0; Offset += 0x10)
+ {
+ Context.Memory.WriteInt32(Position + Offset, 5);
+ }
+
+ return 0;
+ }
+
+ public long StartAudioRenderer(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long StopAudioRenderer(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long QuerySystemEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Friend/IFriendService.cs b/Ryujinx.Core/OsHle/Objects/Friend/IFriendService.cs
new file mode 100644
index 00000000..e98e27ca
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Friend/IFriendService.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Friend
+{
+ class IFriendService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IFriendService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/FspSrv/IDirectory.cs b/Ryujinx.Core/OsHle/Objects/FspSrv/IDirectory.cs
new file mode 100644
index 00000000..88fce28e
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/FspSrv/IDirectory.cs
@@ -0,0 +1,133 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Ipc;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Core.OsHle.Objects.FspSrv
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x310)]
+ struct DirectoryEntry
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x300)]
+ public byte[] Name;
+ public int Unknown;
+ public byte Type;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)]
+ public byte[] Padding;
+ public long Size;
+ }
+
+ enum DirectoryEntryType
+ {
+ Directory,
+ File
+ }
+
+ class IDirectory : IIpcInterface
+ {
+ private List<DirectoryEntry> DirectoryEntries = new List<DirectoryEntry>();
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private string HostPath;
+
+ public IDirectory(string HostPath, int flags)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read },
+ { 1, GetEntryCount }
+ };
+
+ this.HostPath = HostPath;
+
+ if ((flags & 1) == 1)
+ {
+ string[] Directories = Directory.GetDirectories(HostPath, "*", SearchOption.TopDirectoryOnly).
+ Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray();
+
+ foreach (string Directory in Directories)
+ {
+ DirectoryEntry Info = new DirectoryEntry
+ {
+ Name = Encoding.UTF8.GetBytes(Directory),
+ Type = (byte)DirectoryEntryType.Directory,
+ Size = 0
+ };
+
+ Array.Resize(ref Info.Name, 0x300);
+ DirectoryEntries.Add(Info);
+ }
+ }
+
+ if ((flags & 2) == 2)
+ {
+ string[] Files = Directory.GetFiles(HostPath, "*", SearchOption.TopDirectoryOnly).
+ Where(x => (new FileInfo(x).Attributes & FileAttributes.Hidden) == 0).ToArray();
+
+ foreach (string FileName in Files)
+ {
+ DirectoryEntry Info = new DirectoryEntry
+ {
+ Name = Encoding.UTF8.GetBytes(Path.GetFileName(FileName)),
+ Type = (byte)DirectoryEntryType.File,
+ Size = new FileInfo(Path.Combine(HostPath, FileName)).Length
+ };
+
+ Array.Resize(ref Info.Name, 0x300);
+ DirectoryEntries.Add(Info);
+ }
+ }
+ }
+
+ private int LastItem = 0;
+ public long Read(ServiceCtx Context)
+ {
+ long BufferPosition = Context.Request.ReceiveBuff[0].Position;
+ long BufferLen = Context.Request.ReceiveBuff[0].Size;
+ long MaxDirectories = BufferLen / Marshal.SizeOf(typeof(DirectoryEntry));
+
+ if (MaxDirectories > DirectoryEntries.Count - LastItem)
+ {
+ MaxDirectories = DirectoryEntries.Count - LastItem;
+ }
+
+ int CurrentIndex;
+ for (CurrentIndex = 0; CurrentIndex < MaxDirectories; CurrentIndex++)
+ {
+ int CurrentItem = LastItem + CurrentIndex;
+
+ byte[] DirectoryEntry = new byte[Marshal.SizeOf(typeof(DirectoryEntry))];
+ IntPtr Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DirectoryEntry)));
+ Marshal.StructureToPtr(DirectoryEntries[CurrentItem], Ptr, true);
+ Marshal.Copy(Ptr, DirectoryEntry, 0, Marshal.SizeOf(typeof(DirectoryEntry)));
+ Marshal.FreeHGlobal(Ptr);
+
+ AMemoryHelper.WriteBytes(Context.Memory, BufferPosition + Marshal.SizeOf(typeof(DirectoryEntry)) * CurrentIndex, DirectoryEntry);
+ }
+
+ if (LastItem < DirectoryEntries.Count)
+ {
+ LastItem += CurrentIndex;
+ Context.ResponseData.Write((long)CurrentIndex); // index = number of entries written this call.
+ }
+ else
+ {
+ Context.ResponseData.Write((long)0);
+ }
+
+ return 0;
+ }
+
+ public long GetEntryCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)DirectoryEntries.Count);
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.Core/OsHle/Objects/FspSrv/IFile.cs b/Ryujinx.Core/OsHle/Objects/FspSrv/IFile.cs
new file mode 100644
index 00000000..95fbc650
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/FspSrv/IFile.cs
@@ -0,0 +1,93 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Ipc;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Objects.FspSrv
+{
+ class IFile : IIpcInterface, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private Stream BaseStream;
+
+ public IFile(Stream BaseStream)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read },
+ { 1, Write },
+ // { 2, Flush },
+ { 3, SetSize },
+ { 4, GetSize }
+ };
+
+ this.BaseStream = BaseStream;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long Position = Context.Request.ReceiveBuff[0].Position;
+
+ long Zero = Context.RequestData.ReadInt64();
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ byte[] Data = new byte[Size];
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+ int ReadSize = BaseStream.Read(Data, 0, (int)Size);
+
+ AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
+
+ Context.ResponseData.Write((long)ReadSize);
+
+ return 0;
+ }
+
+ public long Write(ServiceCtx Context)
+ {
+ long Position = Context.Request.SendBuff[0].Position;
+
+ long Zero = Context.RequestData.ReadInt64();
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, (int)Size);
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+ BaseStream.Write(Data, 0, (int)Size);
+
+ return 0;
+ }
+
+ public long GetSize(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(BaseStream.Length);
+ return 0;
+ }
+
+ public long SetSize(ServiceCtx Context)
+ {
+ long Size = Context.RequestData.ReadInt64();
+ BaseStream.SetLength(Size);
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && BaseStream != null)
+ {
+ BaseStream.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/FspSrv/IFileSystem.cs b/Ryujinx.Core/OsHle/Objects/FspSrv/IFileSystem.cs
new file mode 100644
index 00000000..68b15845
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/FspSrv/IFileSystem.cs
@@ -0,0 +1,247 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Objects.FspSrv
+{
+ class IFileSystem : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private string Path;
+
+ public IFileSystem(string Path)
+ {
+ //TODO: implement.
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateFile },
+ { 1, DeleteFile },
+ { 2, CreateDirectory },
+ { 3, DeleteDirectory },
+ { 4, DeleteDirectoryRecursively },
+ { 5, RenameFile },
+ { 6, RenameDirectory },
+ { 7, GetEntryType },
+ { 8, OpenFile },
+ { 9, OpenDirectory },
+ { 10, Commit },
+ //{ 11, GetFreeSpaceSize },
+ //{ 12, GetTotalSpaceSize },
+ //{ 13, CleanDirectoryRecursively },
+ //{ 14, GetFileTimeStampRaw }
+ };
+
+ this.Path = Path;
+ }
+
+ public long CreateFile(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+ ulong Mode = Context.RequestData.ReadUInt64();
+ uint Size = Context.RequestData.ReadUInt32();
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName != null)
+ {
+ FileStream NewFile = File.Create(FileName);
+ NewFile.SetLength(Size);
+ NewFile.Close();
+ return 0;
+ }
+
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ public long DeleteFile(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName != null)
+ {
+ File.Delete(FileName);
+ return 0;
+ }
+
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ public long CreateDirectory(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName != null)
+ {
+ Directory.CreateDirectory(FileName);
+ return 0;
+ }
+
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ public long DeleteDirectory(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName != null)
+ {
+ Directory.Delete(FileName);
+ return 0;
+ }
+
+ // TODO: Correct error code.
+ return -1;
+ }
+
+ public long DeleteDirectoryRecursively(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName != null)
+ {
+ Directory.Delete(FileName, true); // recursive = true
+ return 0;
+ }
+
+ // TODO: Correct error code.
+ return -1;
+ }
+
+ public long RenameFile(ServiceCtx Context)
+ {
+ long OldPosition = Context.Request.PtrBuff[0].Position;
+ long NewPosition = Context.Request.PtrBuff[0].Position;
+ string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition);
+ string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition);
+ string OldFileName = Context.Ns.VFs.GetFullPath(Path, OldName);
+ string NewFileName = Context.Ns.VFs.GetFullPath(Path, NewName);
+
+ if (OldFileName != null && NewFileName != null)
+ {
+ File.Move(OldFileName, NewFileName);
+ return 0;
+ }
+
+ // TODO: Correct error code.
+ return -1;
+ }
+
+ public long RenameDirectory(ServiceCtx Context)
+ {
+ long OldPosition = Context.Request.PtrBuff[0].Position;
+ long NewPosition = Context.Request.PtrBuff[0].Position;
+ string OldName = AMemoryHelper.ReadAsciiString(Context.Memory, OldPosition);
+ string NewName = AMemoryHelper.ReadAsciiString(Context.Memory, NewPosition);
+ string OldDirName = Context.Ns.VFs.GetFullPath(Path, OldName);
+ string NewDirName = Context.Ns.VFs.GetFullPath(Path, NewName);
+
+ if (OldDirName != null && NewDirName != null)
+ {
+ Directory.Move(OldDirName, NewDirName);
+ return 0;
+ }
+
+ // TODO: Correct error code.
+ return -1;
+ }
+
+ public long GetEntryType(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName == null)
+ {
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ bool IsFile = File.Exists(FileName);
+
+ Context.ResponseData.Write(IsFile ? 1 : 0);
+
+ return 0;
+ }
+
+ public long OpenFile(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int FilterFlags = Context.RequestData.ReadInt32();
+
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+
+ string FileName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if (FileName == null)
+ {
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ if (File.Exists(FileName))
+ {
+ FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate);
+ MakeObject(Context, new IFile(Stream));
+
+ return 0;
+ }
+
+ //TODO: Correct error code.
+ return -1;
+ }
+
+ public long OpenDirectory(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int FilterFlags = Context.RequestData.ReadInt32();
+
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position);
+
+ string DirName = Context.Ns.VFs.GetFullPath(Path, Name);
+
+ if(DirName != null)
+ {
+ if (Directory.Exists(DirName))
+ {
+ MakeObject(Context, new IDirectory(DirName, FilterFlags));
+ return 0;
+ }
+ else
+ {
+ // TODO: correct error code.
+ return -1;
+ }
+ }
+
+ // TODO: Correct error code.
+ return -1;
+ }
+
+ public long Commit(ServiceCtx Context)
+ {
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/FspSrv/IStorage.cs b/Ryujinx.Core/OsHle/Objects/FspSrv/IStorage.cs
new file mode 100644
index 00000000..4eca37e8
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/FspSrv/IStorage.cs
@@ -0,0 +1,52 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Objects.FspSrv
+{
+ class IStorage : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private Stream BaseStream;
+
+ public IStorage(Stream BaseStream)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read }
+ };
+
+ this.BaseStream = BaseStream;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ if (Context.Request.ReceiveBuff.Count > 0)
+ {
+ IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0];
+
+ //Use smaller length to avoid overflows.
+ if (Size > BuffDesc.Size)
+ {
+ Size = BuffDesc.Size;
+ }
+
+ byte[] Data = new byte[Size];
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+ BaseStream.Read(Data, 0, Data.Length);
+
+ AMemoryHelper.WriteBytes(Context.Memory, BuffDesc.Position, Data);
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Hid/IAppletResource.cs b/Ryujinx.Core/OsHle/Objects/Hid/IAppletResource.cs
new file mode 100644
index 00000000..d6e8947b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Hid/IAppletResource.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Hid
+{
+ class IAppletResource : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public HSharedMem Handle;
+
+ public IAppletResource(HSharedMem Handle)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetSharedMemoryHandle }
+ };
+
+ this.Handle = Handle;
+ }
+
+ public static long GetSharedMemoryHandle(ServiceCtx Context)
+ {
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.HidHandle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/IIpcInterface.cs b/Ryujinx.Core/OsHle/Objects/IIpcInterface.cs
new file mode 100644
index 00000000..c57a0974
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/IIpcInterface.cs
@@ -0,0 +1,10 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects
+{
+ interface IIpcInterface
+ {
+ IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/ObjHelper.cs b/Ryujinx.Core/OsHle/Objects/ObjHelper.cs
new file mode 100644
index 00000000..a151a287
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/ObjHelper.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+
+namespace Ryujinx.Core.OsHle.Objects
+{
+ static class ObjHelper
+ {
+ public static void MakeObject(ServiceCtx Context, object Obj)
+ {
+ if (Context.Session is HDomain Dom)
+ {
+ Context.Response.ResponseObjIds.Add(Dom.GenerateObjectId(Obj));
+ }
+ else
+ {
+ HSessionObj HndData = new HSessionObj(Context.Session, Obj);
+
+ int VHandle = Context.Ns.Os.Handles.GenerateId(HndData);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(VHandle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Parcel.cs b/Ryujinx.Core/OsHle/Objects/Parcel.cs
new file mode 100644
index 00000000..cd7179e1
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Parcel.cs
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle.Objects.Android
+{
+ static class Parcel
+ {
+ public static byte[] GetParcelData(byte[] Parcel)
+ {
+ if (Parcel == null)
+ {
+ throw new ArgumentNullException(nameof(Parcel));
+ }
+
+ using (MemoryStream MS = new MemoryStream(Parcel))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ int DataSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int ObjsSize = Reader.ReadInt32();
+ int ObjsOffset = Reader.ReadInt32();
+
+ MS.Seek(DataOffset - 0x10, SeekOrigin.Current);
+
+ return Reader.ReadBytes(DataSize);
+ }
+ }
+
+ public static byte[] MakeParcel(byte[] Data, byte[] Objs)
+ {
+ if (Data == null)
+ {
+ throw new ArgumentNullException(nameof(Data));
+ }
+
+ if (Objs == null)
+ {
+ throw new ArgumentNullException(nameof(Objs));
+ }
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ Writer.Write(Data.Length);
+ Writer.Write(0x10);
+ Writer.Write(Objs.Length);
+ Writer.Write(Data.Length + 0x10);
+
+ Writer.Write(Data);
+ Writer.Write(Objs);
+
+ return MS.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Time/ISteadyClock.cs b/Ryujinx.Core/OsHle/Objects/Time/ISteadyClock.cs
new file mode 100644
index 00000000..1116b849
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Time/ISteadyClock.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Time
+{
+ class ISteadyClock : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISteadyClock()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Time/ISystemClock.cs b/Ryujinx.Core/OsHle/Objects/Time/ISystemClock.cs
new file mode 100644
index 00000000..3aa70691
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Time/ISystemClock.cs
@@ -0,0 +1,42 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Time
+{
+ class ISystemClock : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private SystemClockType ClockType;
+
+ public ISystemClock(SystemClockType ClockType)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCurrentTime }
+ };
+
+ this.ClockType = ClockType;
+ }
+
+ public long GetCurrentTime(ServiceCtx Context)
+ {
+ DateTime CurrentTime = DateTime.Now;
+
+ if (ClockType == SystemClockType.User ||
+ ClockType == SystemClockType.Network)
+ {
+ CurrentTime = CurrentTime.ToUniversalTime();
+ }
+
+ Context.ResponseData.Write((long)(DateTime.Now - Epoch).TotalSeconds);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Time/ITimeZoneService.cs b/Ryujinx.Core/OsHle/Objects/Time/ITimeZoneService.cs
new file mode 100644
index 00000000..c083628b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Time/ITimeZoneService.cs
@@ -0,0 +1,20 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Time
+{
+ class ITimeZoneService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ITimeZoneService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Time/SystemClockType.cs b/Ryujinx.Core/OsHle/Objects/Time/SystemClockType.cs
new file mode 100644
index 00000000..f447ca1c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Time/SystemClockType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Core.OsHle.Objects.Time
+{
+ enum SystemClockType
+ {
+ User,
+ Network,
+ Local
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Vi/IApplicationDisplayService.cs b/Ryujinx.Core/OsHle/Objects/Vi/IApplicationDisplayService.cs
new file mode 100644
index 00000000..b3ec0e90
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Vi/IApplicationDisplayService.cs
@@ -0,0 +1,176 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Objects.Vi
+{
+ class IApplicationDisplayService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 100, GetRelayService },
+ { 101, GetSystemDisplayService },
+ { 102, GetManagerDisplayService },
+ { 103, GetIndirectDisplayTransactionService },
+ { 1010, OpenDisplay },
+ { 2020, OpenLayer },
+ { 2030, CreateStrayLayer },
+ { 2101, SetLayerScalingMode },
+ { 5202, GetDisplayVSyncEvent }
+ };
+ }
+
+ public long GetRelayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IHOSBinderDriver());
+
+ return 0;
+ }
+
+ public long GetSystemDisplayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemDisplayService());
+
+ return 0;
+ }
+
+ public long GetManagerDisplayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IManagerDisplayService());
+
+ return 0;
+ }
+
+ public long GetIndirectDisplayTransactionService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IHOSBinderDriver());
+
+ return 0;
+ }
+
+ public long OpenDisplay(ServiceCtx Context)
+ {
+ string Name = GetDisplayName(Context);
+
+ long DisplayId = Context.Ns.Os.Displays.GenerateId(new Display(Name));
+
+ Context.ResponseData.Write(DisplayId);
+
+ return 0;
+ }
+
+ public long OpenLayer(ServiceCtx Context)
+ {
+ long LayerId = Context.RequestData.ReadInt64();
+ long UserId = Context.RequestData.ReadInt64();
+
+ long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
+
+ byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
+
+ AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel);
+
+ Context.ResponseData.Write((long)Parcel.Length);
+
+ return 0;
+ }
+
+ public long CreateStrayLayer(ServiceCtx Context)
+ {
+ long LayerFlags = Context.RequestData.ReadInt64();
+ long DisplayId = Context.RequestData.ReadInt64();
+
+ long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
+
+ Display Disp = Context.Ns.Os.Displays.GetData<Display>((int)DisplayId);
+
+ byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
+
+ AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel);
+
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write((long)Parcel.Length);
+
+ return 0;
+ }
+
+ public long SetLayerScalingMode(ServiceCtx Context)
+ {
+ int ScalingMode = Context.RequestData.ReadInt32();
+ long Unknown = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public long GetDisplayVSyncEvent(ServiceCtx Context)
+ {
+ string Name = GetDisplayName(Context);
+
+ int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent());
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ private byte[] MakeIGraphicsBufferProducer(long BasePtr)
+ {
+ long Id = 0x20;
+ long CookiePtr = 0L;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ //flat_binder_object (size is 0x28)
+ Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER)
+ Writer.Write(0); //Flags
+ Writer.Write((int)(Id >> 0));
+ Writer.Write((int)(Id >> 32));
+ Writer.Write((int)(CookiePtr >> 0));
+ Writer.Write((int)(CookiePtr >> 32));
+ Writer.Write((byte)'d');
+ Writer.Write((byte)'i');
+ Writer.Write((byte)'s');
+ Writer.Write((byte)'p');
+ Writer.Write((byte)'d');
+ Writer.Write((byte)'r');
+ Writer.Write((byte)'v');
+ Writer.Write((byte)'\0');
+ Writer.Write(0L); //Pad
+
+ return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 });
+ }
+ }
+
+ private string GetDisplayName(ServiceCtx Context)
+ {
+ string Name = string.Empty;
+
+ for (int Index = 0; Index < 8 &&
+ Context.RequestData.BaseStream.Position <
+ Context.RequestData.BaseStream.Length; Index++)
+ {
+ byte Chr = Context.RequestData.ReadByte();
+
+ if (Chr >= 0x20 && Chr < 0x7f)
+ {
+ Name += (char)Chr;
+ }
+ }
+
+ return Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs b/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs
new file mode 100644
index 00000000..cfd271e8
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Vi/IHOSBinderDriver.cs
@@ -0,0 +1,214 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using Ryujinx.Core.OsHle.Utilities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using static Ryujinx.Core.OsHle.Objects.Android.Parcel;
+
+namespace Ryujinx.Core.OsHle.Objects.Vi
+{
+ class IHOSBinderDriver : IIpcInterface
+ {
+ private delegate long ServiceProcessParcel(ServiceCtx Context, byte[] ParcelData);
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ private Dictionary<(string, int), ServiceProcessParcel> m_Methods;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private class BufferObj
+ {
+
+ }
+
+ private IdPoolWithObj BufferSlots;
+
+ private byte[] Gbfr;
+
+ public IHOSBinderDriver()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, TransactParcel },
+ { 1, AdjustRefcount },
+ { 2, GetNativeHandle }
+ };
+
+ m_Methods = new Dictionary<(string, int), ServiceProcessParcel>()
+ {
+ { ("android.gui.IGraphicBufferProducer", 0x1), GraphicBufferProducerRequestBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x3), GraphicBufferProducerDequeueBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x7), GraphicBufferProducerQueueBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x8), GraphicBufferProducerCancelBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x9), GraphicBufferProducerQuery },
+ { ("android.gui.IGraphicBufferProducer", 0xa), GraphicBufferProducerConnect },
+ { ("android.gui.IGraphicBufferProducer", 0xe), GraphicBufferPreallocateBuffer }
+ };
+
+ BufferSlots = new IdPoolWithObj();
+ }
+
+ public long TransactParcel(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ int Code = Context.RequestData.ReadInt32();
+
+ long DataPos = Context.Request.SendBuff[0].Position;
+ long DataSize = Context.Request.SendBuff[0].Size;
+
+ byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize);
+
+ Data = GetParcelData(Data);
+
+ using (MemoryStream MS = new MemoryStream(Data))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ MS.Seek(4, SeekOrigin.Current);
+
+ int StrSize = Reader.ReadInt32();
+
+ string InterfaceName = Encoding.Unicode.GetString(Data, 8, StrSize * 2);
+
+ if (m_Methods.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq))
+ {
+ return ProcReq(Context, Data);
+ }
+ else
+ {
+ throw new NotImplementedException($"{InterfaceName} {Code}");
+ }
+ }
+ }
+
+ private long GraphicBufferProducerRequestBuffer(ServiceCtx Context, byte[] ParcelData)
+ {
+ int GbfrSize = Gbfr?.Length ?? 0;
+
+ byte[] Data = new byte[GbfrSize + 4];
+
+ if (Gbfr != null)
+ {
+ Buffer.BlockCopy(Gbfr, 0, Data, 0, GbfrSize);
+ }
+
+ return MakeReplyParcel(Context, Data);
+ }
+
+ private long GraphicBufferProducerDequeueBuffer(ServiceCtx Context, byte[] ParcelData)
+ {
+ //Note: It seems that the maximum number of slots is 64, because if we return
+ //a Slot number > 63, it seems to cause a buffer overrun and it reads garbage.
+ //Note 2: The size of each object associated with the slot is 0x30.
+ int Slot = BufferSlots.GenerateId(new BufferObj());
+
+ return MakeReplyParcel(Context, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+
+ private long GraphicBufferProducerQueueBuffer(ServiceCtx Context, byte[] ParcelData)
+ {
+ return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+ }
+
+ private long GraphicBufferProducerCancelBuffer(ServiceCtx Context, byte[] ParcelData)
+ {
+ using (MemoryStream MS = new MemoryStream(ParcelData))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ MS.Seek(0x50, SeekOrigin.Begin);
+
+ int Slot = Reader.ReadInt32();
+
+ BufferSlots.Delete(Slot);
+
+ return MakeReplyParcel(Context, 0);
+ }
+ }
+
+ private long GraphicBufferProducerQuery(ServiceCtx Context, byte[] ParcelData)
+ {
+ return MakeReplyParcel(Context, 0, 0);
+ }
+
+ private long GraphicBufferProducerConnect(ServiceCtx Context, byte[] ParcelData)
+ {
+ return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+ }
+
+ private long GraphicBufferPreallocateBuffer(ServiceCtx Context, byte[] ParcelData)
+ {
+ int GbfrSize = ParcelData.Length - 0x54;
+
+ Gbfr = new byte[GbfrSize];
+
+ Buffer.BlockCopy(ParcelData, 0x54, Gbfr, 0, GbfrSize);
+
+ using (MemoryStream MS = new MemoryStream(ParcelData))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ MS.Seek(0xd4, SeekOrigin.Begin);
+
+ int Handle = Reader.ReadInt32();
+
+ HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
+
+ Context.Ns.Gpu.Renderer.FrameBufferPtr = NvMap.Address;
+ }
+
+ return MakeReplyParcel(Context, 0);
+ }
+
+ private long MakeReplyParcel(ServiceCtx Context, params int[] Ints)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ foreach (int Int in Ints)
+ {
+ Writer.Write(Int);
+ }
+
+ return MakeReplyParcel(Context, MS.ToArray());
+ }
+ }
+
+ private long MakeReplyParcel(ServiceCtx Context, byte[] Data)
+ {
+ long ReplyPos = Context.Request.ReceiveBuff[0].Position;
+ long ReplySize = Context.Request.ReceiveBuff[0].Position;
+
+ byte[] Reply = MakeParcel(Data, new byte[0]);
+
+ AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply);
+
+ return 0;
+ }
+
+ public long AdjustRefcount(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ int AddVal = Context.RequestData.ReadInt32();
+ int Type = Context.RequestData.ReadInt32();
+
+ return 0;
+ }
+
+ public long GetNativeHandle(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ uint Unk = Context.RequestData.ReadUInt32();
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Vi/IManagerDisplayService.cs b/Ryujinx.Core/OsHle/Objects/Vi/IManagerDisplayService.cs
new file mode 100644
index 00000000..f1b3cdd0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Vi/IManagerDisplayService.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Vi
+{
+ class IManagerDisplayService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManagerDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2010, CreateManagedLayer },
+ { 6000, AddToLayerStack }
+ };
+ }
+
+ public static long CreateManagedLayer(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0L); //LayerId
+
+ return 0;
+ }
+
+ public static long AddToLayerStack(ServiceCtx Context)
+ {
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Objects/Vi/ISystemDisplayService.cs b/Ryujinx.Core/OsHle/Objects/Vi/ISystemDisplayService.cs
new file mode 100644
index 00000000..4c83c25f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Objects/Vi/ISystemDisplayService.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Core.OsHle.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Objects.Vi
+{
+ class ISystemDisplayService : IIpcInterface
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2205, SetLayerZ }
+ };
+ }
+
+ public static long SetLayerZ(ServiceCtx Context)
+ {
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Process.cs b/Ryujinx.Core/OsHle/Process.cs
new file mode 100644
index 00000000..84267885
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Process.cs
@@ -0,0 +1,257 @@
+using ChocolArm64;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.Loaders;
+using Ryujinx.Core.Loaders.Executables;
+using Ryujinx.Core.OsHle.Exceptions;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Svc;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle
+{
+ public class Process : IDisposable
+ {
+ private const int MaxStackSize = 8 * 1024 * 1024;
+
+ private const int TlsSize = 0x200;
+ private const int TotalTlsSlots = 32;
+ private const int TlsTotalSize = TotalTlsSlots * TlsSize;
+ private const long TlsPageAddr = (AMemoryMgr.AddrSize - TlsTotalSize) & ~AMemoryMgr.PageMask;
+
+ private Switch Ns;
+
+ public int ProcessId { get; private set; }
+
+ public AMemory Memory { get; private set; }
+
+ public KProcessScheduler Scheduler { get; private set; }
+
+ private SvcHandler SvcHandler;
+
+ private ConcurrentDictionary<int, AThread> TlsSlots;
+
+ private ConcurrentDictionary<long, HThread> ThreadsByTpidr;
+
+ private List<Executable> Executables;
+
+ private HThread MainThread;
+
+ private long ImageBase;
+
+ public Process(Switch Ns, AMemoryAlloc Allocator, int ProcessId)
+ {
+ this.Ns = Ns;
+ this.ProcessId = ProcessId;
+
+ Memory = new AMemory(Ns.Ram, Allocator);
+
+ Scheduler = new KProcessScheduler();
+
+ SvcHandler = new SvcHandler(Ns, this);
+
+ TlsSlots = new ConcurrentDictionary<int, AThread>();
+
+ ThreadsByTpidr = new ConcurrentDictionary<long, HThread>();
+
+ Executables = new List<Executable>();
+
+ ImageBase = 0x8000000;
+
+ Memory.Manager.MapPhys(
+ TlsPageAddr,
+ TlsTotalSize,
+ (int)MemoryType.ThreadLocal,
+ AMemoryPerm.RW);
+ }
+
+ public void LoadProgram(IExecutable Program)
+ {
+ Logging.Info($"Image base at 0x{ImageBase:x16}.");
+
+ Executable Executable = new Executable(Program, Memory, ImageBase);
+
+ Executables.Add(Executable);
+
+ ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd);
+ }
+
+ public void SetEmptyArgs()
+ {
+ ImageBase += AMemoryMgr.PageSize;
+ }
+
+ public void InitializeHeap()
+ {
+ Memory.Manager.SetHeapAddr((ImageBase + 0x3fffffff) & ~0x3fffffff);
+ }
+
+ public bool Run()
+ {
+ if (Executables.Count == 0)
+ {
+ return false;
+ }
+
+ long StackBot = TlsPageAddr - MaxStackSize;
+
+ Memory.Manager.MapPhys(StackBot, MaxStackSize, (int)MemoryType.Normal, AMemoryPerm.RW);
+
+ int Handle = MakeThread(Executables[0].ImageBase, TlsPageAddr, 0, 0, 0);
+
+ if (Handle == -1)
+ {
+ return false;
+ }
+
+ MainThread = Ns.Os.Handles.GetData<HThread>(Handle);
+
+ Scheduler.StartThread(MainThread);
+
+ return true;
+ }
+
+ public void StopAllThreads()
+ {
+ if (MainThread != null)
+ {
+ while (MainThread.Thread.IsAlive)
+ {
+ MainThread.Thread.StopExecution();
+ }
+ }
+
+ foreach (AThread Thread in TlsSlots.Values)
+ {
+ while (Thread.IsAlive)
+ {
+ Thread.StopExecution();
+ }
+ }
+ }
+
+ public int MakeThread(
+ long EntryPoint,
+ long StackTop,
+ long ArgsPtr,
+ int Priority,
+ int ProcessorId)
+ {
+ ThreadPriority ThreadPrio;
+
+ if (Priority < 12)
+ {
+ ThreadPrio = ThreadPriority.Highest;
+ }
+ else if (Priority < 24)
+ {
+ ThreadPrio = ThreadPriority.AboveNormal;
+ }
+ else if (Priority < 36)
+ {
+ ThreadPrio = ThreadPriority.Normal;
+ }
+ else if (Priority < 48)
+ {
+ ThreadPrio = ThreadPriority.BelowNormal;
+ }
+ else
+ {
+ ThreadPrio = ThreadPriority.Lowest;
+ }
+
+ AThread Thread = new AThread(Memory, ThreadPrio, EntryPoint);
+
+ HThread ThreadHnd = new HThread(Thread, ProcessorId, Priority);
+
+ int Handle = Ns.Os.Handles.GenerateId(ThreadHnd);
+
+ int TlsSlot = GetFreeTlsSlot(Thread);
+
+ if (TlsSlot == -1 || Handle == -1)
+ {
+ return -1;
+ }
+
+ Thread.ThreadState.Break += BreakHandler;
+ Thread.ThreadState.SvcCall += SvcHandler.SvcCall;
+ Thread.ThreadState.Undefined += UndefinedHandler;
+ Thread.ThreadState.ProcessId = ProcessId;
+ Thread.ThreadState.ThreadId = Ns.Os.IdGen.GenerateId();
+ Thread.ThreadState.Tpidr = TlsPageAddr + TlsSlot * TlsSize;
+ Thread.ThreadState.X0 = (ulong)ArgsPtr;
+ Thread.ThreadState.X1 = (ulong)Handle;
+ Thread.ThreadState.X31 = (ulong)StackTop;
+
+ Thread.WorkFinished += ThreadFinished;
+
+ ThreadsByTpidr.TryAdd(Thread.ThreadState.Tpidr, ThreadHnd);
+
+ return Handle;
+ }
+
+ private void BreakHandler(object sender, AInstExceptEventArgs e)
+ {
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void UndefinedHandler(object sender, AInstUndEventArgs e)
+ {
+ throw new UndefinedInstructionException(e.Position, e.RawOpCode);
+ }
+
+ private int GetFreeTlsSlot(AThread Thread)
+ {
+ for (int Index = 1; Index < TotalTlsSlots; Index++)
+ {
+ if (TlsSlots.TryAdd(Index, Thread))
+ {
+ return Index;
+ }
+ }
+
+ return -1;
+ }
+
+ private void ThreadFinished(object sender, EventArgs e)
+ {
+ if (sender is AThread Thread)
+ {
+ TlsSlots.TryRemove(GetTlsSlot(Thread.ThreadState.Tpidr), out _);
+
+ Ns.Os.IdGen.DeleteId(Thread.ThreadId);
+ }
+ }
+
+ private int GetTlsSlot(long Position)
+ {
+ return (int)((Position - TlsPageAddr) / TlsSize);
+ }
+
+ public HThread GetThread(long Tpidr)
+ {
+ if (!ThreadsByTpidr.TryGetValue(Tpidr, out HThread Thread))
+ {
+ Logging.Error($"Thread with TPIDR 0x{Tpidr:x16} not found!");
+ }
+
+ return Thread;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ Scheduler.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/ServiceCtx.cs b/Ryujinx.Core/OsHle/ServiceCtx.cs
new file mode 100644
index 00000000..31ecce3d
--- /dev/null
+++ b/Ryujinx.Core/OsHle/ServiceCtx.cs
@@ -0,0 +1,36 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using System.IO;
+
+namespace Ryujinx.Core.OsHle
+{
+ class ServiceCtx
+ {
+ public Switch Ns { get; private set; }
+ public AMemory Memory { get; private set; }
+ public HSession Session { get; private set; }
+ public IpcMessage Request { get; private set; }
+ public IpcMessage Response { get; private set; }
+ public BinaryReader RequestData { get; private set; }
+ public BinaryWriter ResponseData { get; private set; }
+
+ public ServiceCtx(
+ Switch Ns,
+ AMemory Memory,
+ HSession Session,
+ IpcMessage Request,
+ IpcMessage Response,
+ BinaryReader RequestData,
+ BinaryWriter ResponseData)
+ {
+ this.Ns = Ns;
+ this.Memory = Memory;
+ this.Session = Session;
+ this.Request = Request;
+ this.Response = Response;
+ this.RequestData = RequestData;
+ this.ResponseData = ResponseData;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceAcc.cs b/Ryujinx.Core/OsHle/Services/ServiceAcc.cs
new file mode 100644
index 00000000..f25113e5
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceAcc.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Core.OsHle.Objects.Acc;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long AccU0ListOpenUsers(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public static long AccU0GetProfile(ServiceCtx Context)
+ {
+ MakeObject(Context, new IProfile());
+
+ return 0;
+ }
+
+ public static long AccU0InitializeApplicationInfo(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public static long AccU0GetBaasAccountManagerForApplication(ServiceCtx Context)
+ {
+ MakeObject(Context, new IManagerForApplication());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceApm.cs b/Ryujinx.Core/OsHle/Services/ServiceApm.cs
new file mode 100644
index 00000000..e1bc0d34
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceApm.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Core.OsHle.Objects.Apm;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long ApmOpenSession(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISession());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceAppletOE.cs b/Ryujinx.Core/OsHle/Services/ServiceAppletOE.cs
new file mode 100644
index 00000000..bbb2484b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceAppletOE.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Core.OsHle.Objects.Am;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long AppletOpenApplicationProxy(ServiceCtx Context)
+ {
+ MakeObject(Context, new IApplicationProxy());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceAud.cs b/Ryujinx.Core/OsHle/Services/ServiceAud.cs
new file mode 100644
index 00000000..a8ba7dc0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceAud.cs
@@ -0,0 +1,71 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Objects.Aud;
+using System.Text;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long AudOutListAudioOuts(ServiceCtx Context)
+ {
+ long Position = Context.Request.ReceiveBuff[0].Position;
+
+ AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface"));
+
+ Context.ResponseData.Write(1);
+
+ return 0;
+ }
+
+ public static long AudOutOpenAudioOut(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAudioOut());
+
+ Context.ResponseData.Write(48000); //Sample Rate
+ Context.ResponseData.Write(2); //Channel Count
+ Context.ResponseData.Write(2); //PCM Format
+ /*
+ 0 - Invalid
+ 1 - INT8
+ 2 - INT16
+ 3 - INT24
+ 4 - INT32
+ 5 - PCM Float
+ 6 - ADPCM
+ */
+ Context.ResponseData.Write(0); //Unknown
+
+ return 0;
+ }
+
+ public static long AudRenOpenAudioRenderer(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAudioRenderer());
+
+ return 0;
+ }
+
+ public static long AudRenGetAudioRendererWorkBufferSize(ServiceCtx Context)
+ {
+ int SampleRate = Context.RequestData.ReadInt32();
+ int Unknown4 = Context.RequestData.ReadInt32();
+ int Unknown8 = Context.RequestData.ReadInt32();
+ int UnknownC = Context.RequestData.ReadInt32();
+ int Unknown10 = Context.RequestData.ReadInt32();
+ int Unknown14 = Context.RequestData.ReadInt32();
+ int Unknown18 = Context.RequestData.ReadInt32();
+ int Unknown1c = Context.RequestData.ReadInt32();
+ int Unknown20 = Context.RequestData.ReadInt32();
+ int Unknown24 = Context.RequestData.ReadInt32();
+ int Unknown28 = Context.RequestData.ReadInt32();
+ int Unknown2c = Context.RequestData.ReadInt32();
+ int Rev1Magic = Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write(0x400L);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceFriend.cs b/Ryujinx.Core/OsHle/Services/ServiceFriend.cs
new file mode 100644
index 00000000..d1229bd4
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceFriend.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Core.OsHle.Objects.Friend;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long FriendCreateFriendService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFriendService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceFspSrv.cs b/Ryujinx.Core/OsHle/Services/ServiceFspSrv.cs
new file mode 100644
index 00000000..3fe41cf0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceFspSrv.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Core.OsHle.Objects.FspSrv;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long FspSrvInitialize(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public static long FspSrvMountSdCard(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetSdCardPath()));
+
+ return 0;
+ }
+
+ public static long FspSrvMountSaveData(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFileSystem(Context.Ns.VFs.GetGameSavesPath()));
+
+ return 0;
+ }
+
+ public static long FspSrvOpenDataStorageByCurrentProcess(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
+
+ return 0;
+ }
+
+ public static long FspSrvOpenRomStorage(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorage(Context.Ns.VFs.RomFs));
+
+ return 0;
+ }
+
+ public static long FspSrvGetGlobalAccessLogMode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceHid.cs b/Ryujinx.Core/OsHle/Services/ServiceHid.cs
new file mode 100644
index 00000000..4b2e82ff
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceHid.cs
@@ -0,0 +1,56 @@
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Objects.Hid;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long HidCreateAppletResource(ServiceCtx Context)
+ {
+ HSharedMem HidHndData = Context.Ns.Os.Handles.GetData<HSharedMem>(Context.Ns.Os.HidHandle);
+
+ MakeObject(Context, new IAppletResource(HidHndData));
+
+ return 0;
+ }
+
+ public static long HidActivateTouchScreen(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public static long HidSetSupportedNpadStyleSet(ServiceCtx Context)
+ {
+ long Unknown0 = Context.RequestData.ReadInt64();
+ long Unknown8 = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public static long HidSetSupportedNpadIdType(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public static long HidActivateNpad(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public static long HidSetNpadJoyHoldType(ServiceCtx Context)
+ {
+ long Unknown0 = Context.RequestData.ReadInt64();
+ long Unknown8 = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceLm.cs b/Ryujinx.Core/OsHle/Services/ServiceLm.cs
new file mode 100644
index 00000000..1fdde552
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceLm.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long LmInitialize(ServiceCtx Context)
+ {
+ Context.Session.Initialize();
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceNvDrv.cs b/Ryujinx.Core/OsHle/Services/ServiceNvDrv.cs
new file mode 100644
index 00000000..6c5fdaed
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceNvDrv.cs
@@ -0,0 +1,610 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using Ryujinx.Core.OsHle.Utilities;
+using Ryujinx.Graphics.Gpu;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ private delegate long ServiceProcessRequest(ServiceCtx Context);
+
+ private static Dictionary<(string, int), ServiceProcessRequest> IoctlCmds =
+ new Dictionary<(string, int), ServiceProcessRequest>()
+ {
+ { ("/dev/nvhost-as-gpu", 0x4101), NvGpuAsIoctlBindChannel },
+ { ("/dev/nvhost-as-gpu", 0x4102), NvGpuAsIoctlAllocSpace },
+ { ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx },
+ { ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions },
+ { ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx },
+ { ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig },
+ { ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait },
+ { ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize },
+ { ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo },
+ { ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics },
+ { ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks },
+ { ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask },
+ { ("/dev/nvhost-gpu", 0x4714), NvMapIoctlChannelSetUserData },
+ { ("/dev/nvhost-gpu", 0x4801), NvMapIoctlChannelSetNvMap },
+ { ("/dev/nvhost-gpu", 0x4808), NvMapIoctlChannelSubmitGpFifo },
+ { ("/dev/nvhost-gpu", 0x4809), NvMapIoctlChannelAllocObjCtx },
+ { ("/dev/nvhost-gpu", 0x480b), NvMapIoctlChannelZcullBind },
+ { ("/dev/nvhost-gpu", 0x480c), NvMapIoctlChannelSetErrorNotifier },
+ { ("/dev/nvhost-gpu", 0x480d), NvMapIoctlChannelSetPriority },
+ { ("/dev/nvhost-gpu", 0x481a), NvMapIoctlChannelAllocGpFifoEx2 },
+ { ("/dev/nvmap", 0x0101), NvMapIocCreate },
+ { ("/dev/nvmap", 0x0103), NvMapIocFromId },
+ { ("/dev/nvmap", 0x0104), NvMapIocAlloc },
+ { ("/dev/nvmap", 0x0109), NvMapIocParam },
+ { ("/dev/nvmap", 0x010e), NvMapIocGetId },
+ };
+
+ public static long NvDrvOpen(ServiceCtx Context)
+ {
+ long NamePtr = Context.Request.SendBuff[0].Position;
+
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr);
+
+ int Fd = Context.Ns.Os.Fds.GenerateId(new FileDesc(Name));
+
+ Context.ResponseData.Write(Fd);
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public static long NvDrvIoctl(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+ int Cmd = Context.RequestData.ReadInt32() & 0xffff;
+
+ FileDesc FdData = Context.Ns.Os.Fds.GetData<FileDesc>(Fd);
+
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ Context.ResponseData.Write(0);
+
+ if (IoctlCmds.TryGetValue((FdData.Name, Cmd), out ServiceProcessRequest ProcReq))
+ {
+ return ProcReq(Context);
+ }
+ else
+ {
+ throw new NotImplementedException($"{FdData.Name} {Cmd:x4}");
+ }
+ }
+
+ public static long NvDrvClose(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+
+ Context.Ns.Os.Fds.Delete(Fd);
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public static long NvDrvInitialize(ServiceCtx Context)
+ {
+ long TransferMemSize = Context.RequestData.ReadInt64();
+ int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0];
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public static long NvDrvQueryEvent(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+ int EventId = Context.RequestData.ReadInt32();
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(0xcafe);
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public static long NvDrvSetClientPid(ServiceCtx Context)
+ {
+ long Pid = Context.RequestData.ReadInt64();
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ private static long NvGpuAsIoctlBindChannel(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int Fd = Context.Memory.ReadInt32(Position);
+
+ return 0;
+ }
+
+ private static long NvGpuAsIoctlAllocSpace(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int Pages = Reader.ReadInt32();
+ int PageSize = Reader.ReadInt32();
+ int Flags = Reader.ReadInt32();
+ int Padding = Reader.ReadInt32();
+ long Align = Reader.ReadInt64();
+
+ if ((Flags & 1) != 0)
+ {
+ Align = Context.Ns.Gpu.ReserveMemory(Align, (long)Pages * PageSize, 1);
+ }
+ else
+ {
+ Align = Context.Ns.Gpu.ReserveMemory((long)Pages * PageSize, Align);
+ }
+
+ Context.Memory.WriteInt64(Position + 0x10, Align);
+
+ return 0;
+ }
+
+ private static long NvGpuAsIoctlMapBufferEx(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int Flags = Reader.ReadInt32();
+ int Kind = Reader.ReadInt32();
+ int Handle = Reader.ReadInt32();
+ int PageSize = Reader.ReadInt32();
+ long BuffAddr = Reader.ReadInt64();
+ long MapSize = Reader.ReadInt64();
+ long Offset = Reader.ReadInt64();
+
+ HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
+
+ if (NvMap != null)
+ {
+ if ((Flags & 1) != 0)
+ {
+ Offset = Context.Ns.Gpu.MapMemory(NvMap.Address, Offset, NvMap.Size);
+ }
+ else
+ {
+ Offset = Context.Ns.Gpu.MapMemory(NvMap.Address, NvMap.Size);
+ }
+ }
+
+ Context.Memory.WriteInt64(Position + 0x20, Offset);
+
+ return 0;
+ }
+
+ private static long NvGpuAsIoctlGetVaRegions(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+ MemWriter Writer = new MemWriter(Context.Memory, Position);
+
+ long Unused = Reader.ReadInt64();
+ int BuffSize = Reader.ReadInt32();
+ int Padding = Reader.ReadInt32();
+
+ BuffSize = 0x30;
+
+ Writer.WriteInt64(Unused);
+ Writer.WriteInt32(BuffSize);
+ Writer.WriteInt32(Padding);
+
+ Writer.WriteInt64(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt64(0);
+
+ Writer.WriteInt64(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt64(0);
+
+ return 0;
+ }
+
+ private static long NvGpuAsIoctlInitializeEx(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int BigPageSize = Reader.ReadInt32();
+ int AsFd = Reader.ReadInt32();
+ int Flags = Reader.ReadInt32();
+ int Reserved = Reader.ReadInt32();
+ long Unknown10 = Reader.ReadInt64();
+ long Unknown18 = Reader.ReadInt64();
+ long Unknown20 = Reader.ReadInt64();
+
+ return 0;
+ }
+
+ private static long NvHostIoctlCtrlGetConfig(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+ MemWriter Writer = new MemWriter(Context.Memory, Position + 0x82);
+
+ for (int Index = 0; Index < 0x101; Index++)
+ {
+ Writer.WriteByte(0);
+ }
+
+ return 0;
+ }
+
+ private static long NvHostIoctlCtrlEventWait(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int SyncPtId = Reader.ReadInt32();
+ int Threshold = Reader.ReadInt32();
+ int Timeout = Reader.ReadInt32();
+ int Value = Reader.ReadInt32();
+
+ Context.Memory.WriteInt32(Position + 0xc, 0xcafe);
+
+ return 0;
+ }
+
+ private static long NvGpuIoctlZcullGetCtxSize(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ Context.Memory.WriteInt32(Position, 1);
+
+ return 0;
+ }
+
+ private static long NvGpuIoctlZcullGetInfo(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemWriter Writer = new MemWriter(Context.Memory, Position);
+
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+
+ return 0;
+ }
+
+ private static long NvGpuIoctlGetCharacteristics(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+ MemWriter Writer = new MemWriter(Context.Memory, Position);
+
+ //Note: We should just ignore the BuffAddr, because official code
+ //does __memcpy_device from Position + 0x10 to BuffAddr.
+ long BuffSize = Reader.ReadInt64();
+ long BuffAddr = Reader.ReadInt64();
+
+ BuffSize = 0xa0;
+
+ Writer.WriteInt64(BuffSize);
+ Writer.WriteInt64(BuffAddr);
+ Writer.WriteInt32(0x120); //NVGPU_GPU_ARCH_GM200
+ Writer.WriteInt32(0xb); //NVGPU_GPU_IMPL_GM20B
+ Writer.WriteInt32(0xa1);
+ Writer.WriteInt32(1);
+ Writer.WriteInt64(0x40000);
+ Writer.WriteInt64(0);
+ Writer.WriteInt32(2);
+ Writer.WriteInt32(0x20); //NVGPU_GPU_BUS_TYPE_AXI
+ Writer.WriteInt32(0x20000);
+ Writer.WriteInt32(0x20000);
+ Writer.WriteInt32(0x1b);
+ Writer.WriteInt32(0x30000);
+ Writer.WriteInt32(1);
+ Writer.WriteInt32(0x503);
+ Writer.WriteInt32(0x503);
+ Writer.WriteInt32(0x80);
+ Writer.WriteInt32(0x28);
+ Writer.WriteInt32(0);
+ Writer.WriteInt64(0x55);
+ Writer.WriteInt32(0x902d); //FERMI_TWOD_A
+ Writer.WriteInt32(0xb197); //MAXWELL_B
+ Writer.WriteInt32(0xb1c0); //MAXWELL_COMPUTE_B
+ Writer.WriteInt32(0xb06f); //MAXWELL_CHANNEL_GPFIFO_A
+ Writer.WriteInt32(0xa140); //KEPLER_INLINE_TO_MEMORY_B
+ Writer.WriteInt32(0xb0b5); //MAXWELL_DMA_COPY_A
+ Writer.WriteInt32(1);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(2);
+ Writer.WriteInt32(1);
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(1);
+ Writer.WriteInt32(0x21d70);
+ Writer.WriteInt32(0);
+ Writer.WriteByte((byte)'g');
+ Writer.WriteByte((byte)'m');
+ Writer.WriteByte((byte)'2');
+ Writer.WriteByte((byte)'0');
+ Writer.WriteByte((byte)'b');
+ Writer.WriteByte((byte)'\0');
+ Writer.WriteByte((byte)'\0');
+ Writer.WriteByte((byte)'\0');
+ Writer.WriteInt64(0);
+
+ return 0;
+ }
+
+ private static long NvGpuIoctlGetTpcMasks(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int MaskBuffSize = Reader.ReadInt32();
+ int Reserved = Reader.ReadInt32();
+ long MaskBuffAddr = Reader.ReadInt64();
+ long Unknown = Reader.ReadInt64();
+
+ return 0;
+ }
+
+ private static long NvGpuIoctlZbcGetActiveSlotMask(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ Context.Memory.WriteInt32(Position + 0, 7);
+ Context.Memory.WriteInt32(Position + 4, 1);
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelSetUserData(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelSetNvMap(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int Fd = Context.Memory.ReadInt32(Position);
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelSubmitGpFifo(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+ MemWriter Writer = new MemWriter(Context.Memory, Position + 0x10);
+
+ long GpFifo = Reader.ReadInt64();
+ int Count = Reader.ReadInt32();
+ int Flags = Reader.ReadInt32();
+ int FenceId = Reader.ReadInt32();
+ int FenceVal = Reader.ReadInt32();
+
+ for (int Index = 0; Index < Count; Index++)
+ {
+ long GpFifoHdr = Reader.ReadInt64();
+
+ long GpuAddr = GpFifoHdr & 0xffffffffff;
+
+ int Size = (int)(GpFifoHdr >> 40) & 0x7ffffc;
+
+ long CpuAddr = Context.Ns.Gpu.GetCpuAddr(GpuAddr);
+
+ if (CpuAddr != -1)
+ {
+ byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size);
+
+ NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data);
+
+ Context.Ns.Gpu.ProcessPushBuffer(PushBuffer, Context.Memory);
+ }
+ }
+
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelAllocObjCtx(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int ClassNum = Context.Memory.ReadInt32(Position + 0);
+ int Flags = Context.Memory.ReadInt32(Position + 4);
+
+ Context.Memory.WriteInt32(Position + 8, 0);
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelZcullBind(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ long GpuVa = Reader.ReadInt64();
+ int Mode = Reader.ReadInt32();
+ int Padding = Reader.ReadInt32();
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ long Offset = Reader.ReadInt64();
+ long Size = Reader.ReadInt64();
+ int Mem = Reader.ReadInt32();
+ int Padding = Reader.ReadInt32();
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelSetPriority(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ int Priority = Context.Memory.ReadInt32(Position);
+
+ return 0;
+ }
+
+ private static long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context)
+ {
+ long Position = Context.Request.PtrBuff[0].Position;
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+ MemWriter Writer = new MemWriter(Context.Memory, Position + 0xc);
+
+ int Count = Reader.ReadInt32();
+ int Flags = Reader.ReadInt32();
+ int Unknown8 = Reader.ReadInt32();
+ long Fence = Reader.ReadInt64();
+ int Unknown14 = Reader.ReadInt32();
+ int Unknown18 = Reader.ReadInt32();
+
+ Writer.WriteInt32(0);
+ Writer.WriteInt32(0);
+
+ return 0;
+ }
+
+ private static long NvMapIocCreate(ServiceCtx Context)
+ {
+ long Position = Context.Request.GetSendBuffPtr();
+
+ int Size = Context.Memory.ReadInt32(Position);
+
+ int Id = Context.Ns.Os.NvMapIds.GenerateId();
+
+ int Handle = Context.Ns.Os.Handles.GenerateId(new HNvMap(Id, Size));
+
+ Context.Memory.WriteInt32(Position + 4, Handle);
+
+ return 0;
+ }
+
+ private static long NvMapIocFromId(ServiceCtx Context)
+ {
+ long Position = Context.Request.GetSendBuffPtr();
+
+ int Id = Context.Memory.ReadInt32(Position);
+
+ int Handle = -1;
+
+ foreach (KeyValuePair<int, object> KV in Context.Ns.Os.Handles)
+ {
+ if (KV.Value is HNvMap NvMap && NvMap.Id == Id)
+ {
+ Handle = KV.Key;
+
+ break;
+ }
+ }
+
+ Context.Memory.WriteInt32(Position + 4, Handle);
+
+ return 0;
+ }
+
+ private static long NvMapIocAlloc(ServiceCtx Context)
+ {
+ long Position = Context.Request.GetSendBuffPtr();
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int Handle = Reader.ReadInt32();
+ int HeapMask = Reader.ReadInt32();
+ int Flags = Reader.ReadInt32();
+ int Align = Reader.ReadInt32();
+ byte Kind = (byte)Reader.ReadInt64();
+ long Addr = Reader.ReadInt64();
+
+ HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
+
+ if (NvMap != null)
+ {
+ NvMap.Address = Addr;
+ NvMap.Align = Align;
+ NvMap.Kind = Kind;
+ }
+
+ Logging.Debug($"NvMapIocAlloc at {NvMap.Address:x16}");
+
+ return 0;
+ }
+
+ private static long NvMapIocParam(ServiceCtx Context)
+ {
+ long Position = Context.Request.GetSendBuffPtr();
+
+ MemReader Reader = new MemReader(Context.Memory, Position);
+
+ int Handle = Reader.ReadInt32();
+ int Param = Reader.ReadInt32();
+
+ HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
+
+ int Response = 0;
+
+ switch (Param)
+ {
+ case 1: Response = NvMap.Size; break;
+ case 2: Response = NvMap.Align; break;
+ case 4: Response = 0x40000000; break;
+ case 5: Response = NvMap.Kind; break;
+ }
+
+ Context.Memory.WriteInt32(Position + 8, Response);
+
+ return 0;
+ }
+
+ private static long NvMapIocGetId(ServiceCtx Context)
+ {
+ long Position = Context.Request.GetSendBuffPtr();
+
+ int Handle = Context.Memory.ReadInt32(Position + 4);
+
+ HNvMap NvMap = Context.Ns.Os.Handles.GetData<HNvMap>(Handle);
+
+ Context.Memory.WriteInt32(Position, NvMap.Id);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServicePctl.cs b/Ryujinx.Core/OsHle/Services/ServicePctl.cs
new file mode 100644
index 00000000..9c4406bb
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServicePctl.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Core.OsHle.Objects.Am;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long PctlCreateService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IParentalControlService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServicePl.cs b/Ryujinx.Core/OsHle/Services/ServicePl.cs
new file mode 100644
index 00000000..21e6741c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServicePl.cs
@@ -0,0 +1,35 @@
+using Ryujinx.Core.OsHle.Ipc;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long PlGetLoadState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1); //Loaded
+
+ return 0;
+ }
+
+ public static long PlGetFontSize(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(Horizon.FontSize);
+
+ return 0;
+ }
+
+ public static long PlGetSharedMemoryAddressOffset(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public static long PlGetSharedMemoryNativeHandle(ServiceCtx Context)
+ {
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.FontHandle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceSet.cs b/Ryujinx.Core/OsHle/Services/ServiceSet.cs
new file mode 100644
index 00000000..83ea5b2c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceSet.cs
@@ -0,0 +1,32 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ private const int LangCodesCount = 13;
+
+ public static long SetGetAvailableLanguageCodes(ServiceCtx Context)
+ {
+ int PtrBuffSize = Context.RequestData.ReadInt32();
+
+ if (Context.Request.RecvListBuff.Count > 0)
+ {
+ long Position = Context.Request.RecvListBuff[0].Position;
+ short Size = Context.Request.RecvListBuff[0].Size;
+
+ //This should return an array of ints with values matching the LanguageCode enum.
+ byte[] Data = new byte[Size];
+
+ Data[0] = 0;
+ Data[1] = 1;
+
+ AMemoryHelper.WriteBytes(Context.Memory, Position, Data);
+ }
+
+ Context.ResponseData.Write(LangCodesCount);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceSm.cs b/Ryujinx.Core/OsHle/Services/ServiceSm.cs
new file mode 100644
index 00000000..58abed24
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceSm.cs
@@ -0,0 +1,48 @@
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ private const int SmNotInitialized = 0x415;
+
+ public static long SmInitialize(ServiceCtx Context)
+ {
+ Context.Session.Initialize();
+
+ return 0;
+ }
+
+ public static long SmGetService(ServiceCtx Context)
+ {
+ //Only for kernel version > 3.0.0.
+ if (!Context.Session.IsInitialized)
+ {
+ //return SmNotInitialized;
+ }
+
+ string Name = string.Empty;
+
+ for (int Index = 0; Index < 8 &&
+ Context.RequestData.BaseStream.Position <
+ Context.RequestData.BaseStream.Length; Index++)
+ {
+ byte Chr = Context.RequestData.ReadByte();
+
+ if (Chr >= 0x20 && Chr < 0x7f)
+ {
+ Name += (char)Chr;
+ }
+ }
+
+ HSession Session = new HSession(Name);
+
+ int Handle = Context.Ns.Os.Handles.GenerateId(Session);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceTime.cs b/Ryujinx.Core/OsHle/Services/ServiceTime.cs
new file mode 100644
index 00000000..a5fddcba
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceTime.cs
@@ -0,0 +1,45 @@
+using Ryujinx.Core.OsHle.Objects.Time;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long TimeGetStandardUserSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.User));
+
+ return 0;
+ }
+
+ public static long TimeGetStandardNetworkSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.Network));
+
+ return 0;
+ }
+
+ public static long TimeGetStandardSteadyClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISteadyClock());
+
+ return 0;
+ }
+
+ public static long TimeGetTimeZoneService(ServiceCtx Context)
+ {
+ MakeObject(Context, new ITimeZoneService());
+
+ return 0;
+ }
+
+ public static long TimeGetStandardLocalSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.Local));
+
+ return 0;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/ServiceVi.cs b/Ryujinx.Core/OsHle/Services/ServiceVi.cs
new file mode 100644
index 00000000..75cdc31b
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/ServiceVi.cs
@@ -0,0 +1,18 @@
+using Ryujinx.Core.OsHle.Objects.Vi;
+
+using static Ryujinx.Core.OsHle.Objects.ObjHelper;
+
+namespace Ryujinx.Core.OsHle.Services
+{
+ static partial class Service
+ {
+ public static long ViGetDisplayService(ServiceCtx Context)
+ {
+ int Unknown = Context.RequestData.ReadInt32();
+
+ MakeObject(Context, new IApplicationDisplayService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcHandler.cs b/Ryujinx.Core/OsHle/Svc/SvcHandler.cs
new file mode 100644
index 00000000..60af1e11
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcHandler.cs
@@ -0,0 +1,80 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Svc
+{
+ partial class SvcHandler
+ {
+ private delegate void SvcFunc(AThreadState ThreadState);
+
+ private Dictionary<int, SvcFunc> SvcFuncs;
+
+ private Switch Ns;
+ private Process Process;
+ private AMemory Memory;
+
+ private static Random Rng;
+
+ public SvcHandler(Switch Ns, Process Process)
+ {
+ SvcFuncs = new Dictionary<int, SvcFunc>()
+ {
+ { 0x01, SvcSetHeapSize },
+ { 0x03, SvcSetMemoryAttribute },
+ { 0x04, SvcMapMemory },
+ { 0x06, SvcQueryMemory },
+ { 0x07, SvcExitProcess },
+ { 0x08, SvcCreateThread },
+ { 0x09, SvcStartThread },
+ { 0x0b, SvcSleepThread },
+ { 0x0c, SvcGetThreadPriority },
+ { 0x13, SvcMapSharedMemory },
+ { 0x14, SvcUnmapSharedMemory },
+ { 0x15, SvcCreateTransferMemory },
+ { 0x16, SvcCloseHandle },
+ { 0x17, SvcResetSignal },
+ { 0x18, SvcWaitSynchronization },
+ { 0x1a, SvcArbitrateLock },
+ { 0x1b, SvcArbitrateUnlock },
+ { 0x1c, SvcWaitProcessWideKeyAtomic },
+ { 0x1d, SvcSignalProcessWideKey },
+ { 0x1e, SvcGetSystemTick },
+ { 0x1f, SvcConnectToNamedPort },
+ { 0x21, SvcSendSyncRequest },
+ { 0x22, SvcSendSyncRequestWithUserBuffer },
+ { 0x26, SvcBreak },
+ { 0x27, SvcOutputDebugString },
+ { 0x29, SvcGetInfo }
+ };
+
+ this.Ns = Ns;
+ this.Process = Process;
+ this.Memory = Process.Memory;
+ }
+
+ static SvcHandler()
+ {
+ Rng = new Random();
+ }
+
+ public void SvcCall(object sender, AInstExceptEventArgs e)
+ {
+ AThreadState ThreadState = (AThreadState)sender;
+
+ if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
+ {
+ Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} called.");
+
+ Func(ThreadState);
+
+ Logging.Trace($"(Thread {ThreadState.ThreadId}) {Func.Method.Name} ended.");
+ }
+ else
+ {
+ throw new NotImplementedException(e.Id.ToString("x4"));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcMemory.cs b/Ryujinx.Core/OsHle/Svc/SvcMemory.cs
new file mode 100644
index 00000000..7528f4e0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcMemory.cs
@@ -0,0 +1,121 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+
+namespace Ryujinx.Core.OsHle.Svc
+{
+ partial class SvcHandler
+ {
+ private void SvcSetHeapSize(AThreadState ThreadState)
+ {
+ uint Size = (uint)ThreadState.X1;
+
+ Memory.Manager.SetHeapSize(Size, (int)MemoryType.Heap);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ ThreadState.X1 = (ulong)Memory.Manager.HeapAddr;
+ }
+
+ private void SvcSetMemoryAttribute(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+ int State0 = (int)ThreadState.X2;
+ int State1 = (int)ThreadState.X3;
+
+ //TODO
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcMapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ Memory.Manager.MapMirror(Src, Dst, Size, (int)MemoryType.MappedMemory);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcQueryMemory(AThreadState ThreadState)
+ {
+ long InfoPtr = (long)ThreadState.X0;
+ long Position = (long)ThreadState.X2;
+
+ AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
+
+ MemoryInfo Info = new MemoryInfo(MapInfo);
+
+ Memory.WriteInt64(InfoPtr + 0x00, Info.BaseAddress);
+ Memory.WriteInt64(InfoPtr + 0x08, Info.Size);
+ Memory.WriteInt32(InfoPtr + 0x10, Info.MemType);
+ Memory.WriteInt32(InfoPtr + 0x14, Info.MemAttr);
+ Memory.WriteInt32(InfoPtr + 0x18, Info.MemPerm);
+ Memory.WriteInt32(InfoPtr + 0x1c, Info.IpcRefCount);
+ Memory.WriteInt32(InfoPtr + 0x20, Info.DeviceRefCount);
+ Memory.WriteInt32(InfoPtr + 0x24, Info.Padding);
+
+ //TODO: X1.
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ ThreadState.X1 = 0;
+ }
+
+ private void SvcMapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+ int Perm = (int)ThreadState.X3;
+
+ HSharedMem SharedMem = Ns.Os.Handles.GetData<HSharedMem>(Handle);
+
+ if (SharedMem != null)
+ {
+ SharedMem.AddVirtualPosition(Src);
+
+ Memory.Manager.MapPhys(Src, Size, (int)MemoryType.SharedMemory, (AMemoryPerm)Perm);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcUnmapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ HSharedMem HndData = Ns.Os.Handles.GetData<HSharedMem>(Handle);
+
+ if (HndData != null)
+ {
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcCreateTransferMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+ int Perm = (int)ThreadState.X3;
+
+ AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position);
+
+ Memory.Manager.Reprotect(Position, Size, (AMemoryPerm)Perm);
+
+ HTransferMem HndData = new HTransferMem(Memory, MapInfo.Perm, Position, Size);
+
+ int Handle = Ns.Os.Handles.GenerateId(HndData);
+
+ ThreadState.X1 = (ulong)Handle;
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcResult.cs b/Ryujinx.Core/OsHle/Svc/SvcResult.cs
new file mode 100644
index 00000000..a5be9a94
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcResult.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Core.OsHle.Svc
+{
+ enum SvcResult
+ {
+ Success = 0,
+ ErrBadHandle = 0xe401,
+ ErrTimeout = 0xea01,
+ ErrBadInfo = 0xf001,
+ ErrBadIpcReq = 0xf601
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcSystem.cs b/Ryujinx.Core/OsHle/Svc/SvcSystem.cs
new file mode 100644
index 00000000..7f593c8f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcSystem.cs
@@ -0,0 +1,227 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Exceptions;
+using Ryujinx.Core.OsHle.Handles;
+using Ryujinx.Core.OsHle.Ipc;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Svc
+{
+ partial class SvcHandler
+ {
+ private void SvcExitProcess(AThreadState ThreadState) => Ns.Os.ExitProcess(ThreadState.ProcessId);
+
+ private void SvcCloseHandle(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ Ns.Os.CloseHandle(Handle);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcResetSignal(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ //TODO: Implement events.
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcWaitSynchronization(AThreadState ThreadState)
+ {
+ long HandlesPtr = (long)ThreadState.X0;
+ int HandlesCount = (int)ThreadState.X2;
+ long Timeout = (long)ThreadState.X3;
+
+ //TODO: Implement events.
+
+ HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcGetSystemTick(AThreadState ThreadState)
+ {
+ ThreadState.X0 = (ulong)ThreadState.CntpctEl0;
+ }
+
+ private void SvcConnectToNamedPort(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ long NamePtr = (long)ThreadState.X1;
+
+ string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
+
+ //TODO: Validate that app has perms to access the service, and that the service
+ //actually exists, return error codes otherwise.
+
+ HSession Session = new HSession(Name);
+
+ ThreadState.X1 = (ulong)Ns.Os.Handles.GenerateId(Session);
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcSendSyncRequest(AThreadState ThreadState)
+ {
+ SendSyncRequest(ThreadState, false);
+ }
+
+ private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
+ {
+ SendSyncRequest(ThreadState, true);
+ }
+
+ private void SendSyncRequest(AThreadState ThreadState, bool UserBuffer)
+ {
+ long CmdPtr = ThreadState.Tpidr;
+ long Size = 0x100;
+ int Handle = 0;
+
+ if (UserBuffer)
+ {
+ CmdPtr = (long)ThreadState.X0;
+ Size = (long)ThreadState.X1;
+ Handle = (int)ThreadState.X2;
+ }
+ else
+ {
+ Handle = (int)ThreadState.X0;
+ }
+
+ HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
+
+ byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size);
+
+ HSession Session = Ns.Os.Handles.GetData<HSession>(Handle);
+
+ IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr, Session is HDomain);
+
+ if (Session != null)
+ {
+ IpcHandler.IpcCall(Ns, Memory, Session, Cmd, CmdPtr, Handle);
+
+ byte[] Response = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+ else
+ {
+ ThreadState.X0 = (int)SvcResult.ErrBadIpcReq;
+ }
+
+ Thread.Yield();
+
+ Process.Scheduler.Resume(CurrThread);
+ }
+
+ private void SvcBreak(AThreadState ThreadState)
+ {
+ long Reason = (long)ThreadState.X0;
+ long Unknown = (long)ThreadState.X1;
+ long Info = (long)ThreadState.X2;
+
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void SvcOutputDebugString(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ string Str = AMemoryHelper.ReadAsciiString(Memory, Position, (int)Size);
+
+ Logging.Info($"SvcOutputDebugString: {Str}");
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcGetInfo(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ int InfoType = (int)ThreadState.X1;
+ long Handle = (long)ThreadState.X2;
+ int InfoId = (int)ThreadState.X3;
+
+ //Fail for info not available on older Kernel versions.
+ if (InfoType == 18 ||
+ InfoType == 19)
+ {
+ ThreadState.X0 = (int)SvcResult.ErrBadInfo;
+
+ return;
+ }
+
+ switch (InfoType)
+ {
+ case 2: ThreadState.X1 = GetMapRegionBaseAddr(); break;
+ case 3: ThreadState.X1 = GetMapRegionSize(); break;
+ case 4: ThreadState.X1 = GetHeapRegionBaseAddr(); break;
+ case 5: ThreadState.X1 = GetHeapRegionSize(); break;
+ case 6: ThreadState.X1 = GetTotalMem(); break;
+ case 7: ThreadState.X1 = GetUsedMem(); break;
+ case 11: ThreadState.X1 = GetRnd64(); break;
+ case 12: ThreadState.X1 = GetAddrSpaceBaseAddr(); break;
+ case 13: ThreadState.X1 = GetAddrSpaceSize(); break;
+ case 14: ThreadState.X1 = GetMapRegionBaseAddr(); break;
+ case 15: ThreadState.X1 = GetMapRegionSize(); break;
+
+ default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}");
+ }
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private ulong GetTotalMem()
+ {
+ return (ulong)Memory.Manager.GetTotalMemorySize();
+ }
+
+ private ulong GetUsedMem()
+ {
+ return (ulong)Memory.Manager.GetUsedMemorySize();
+ }
+
+ private ulong GetRnd64()
+ {
+ return (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
+ }
+
+ private ulong GetAddrSpaceBaseAddr()
+ {
+ return 0x08000000;
+ }
+
+ private ulong GetAddrSpaceSize()
+ {
+ return AMemoryMgr.AddrSize - GetAddrSpaceBaseAddr();
+ }
+
+ private ulong GetMapRegionBaseAddr()
+ {
+ return 0x80000000;
+ }
+
+ private ulong GetMapRegionSize()
+ {
+ return 0x40000000;
+ }
+
+ private ulong GetHeapRegionBaseAddr()
+ {
+ return GetMapRegionBaseAddr() + GetMapRegionSize();
+ }
+
+ private ulong GetHeapRegionSize()
+ {
+ return 0x40000000;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcThread.cs b/Ryujinx.Core/OsHle/Svc/SvcThread.cs
new file mode 100644
index 00000000..a635edb1
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcThread.cs
@@ -0,0 +1,85 @@
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+
+namespace Ryujinx.Core.OsHle.Svc
+{
+ partial class SvcHandler
+ {
+ private void SvcCreateThread(AThreadState ThreadState)
+ {
+ long EntryPoint = (long)ThreadState.X1;
+ long ArgsPtr = (long)ThreadState.X2;
+ long StackTop = (long)ThreadState.X3;
+ int Priority = (int)ThreadState.X4;
+ int ProcessorId = (int)ThreadState.X5;
+
+ if (Ns.Os.TryGetProcess(ThreadState.ProcessId, out Process Process))
+ {
+ if (ProcessorId == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ ProcessorId = 0;
+ }
+
+ int Handle = Process.MakeThread(
+ EntryPoint,
+ StackTop,
+ ArgsPtr,
+ Priority,
+ ProcessorId);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcStartThread(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ HThread Thread = Ns.Os.Handles.GetData<HThread>(Handle);
+
+ if (Thread != null)
+ {
+ Process.Scheduler.StartThread(Thread);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ //TODO: Error codes.
+ }
+
+ private void SvcSleepThread(AThreadState ThreadState)
+ {
+ ulong NanoSecs = ThreadState.X0;
+
+ HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (NanoSecs == 0)
+ {
+ Process.Scheduler.Yield(CurrThread);
+ }
+ else
+ {
+ Process.Scheduler.WaitForSignal(CurrThread, (int)(NanoSecs / 1000000));
+ }
+ }
+
+ private void SvcGetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ HThread Thread = Ns.Os.Handles.GetData<HThread>(Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X1 = (ulong)Thread.Priority;
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ //TODO: Error codes.
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs
new file mode 100644
index 00000000..dec13f75
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Svc/SvcThreadSync.cs
@@ -0,0 +1,78 @@
+using ChocolArm64.State;
+using Ryujinx.Core.OsHle.Handles;
+
+namespace Ryujinx.Core.OsHle.Svc
+{
+ partial class SvcHandler
+ {
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int RequestingThreadHandle = (int)ThreadState.X2;
+
+ HThread RequestingThread = Ns.Os.Handles.GetData<HThread>(RequestingThreadHandle);
+
+ Mutex M = new Mutex(Process, MutexAddress, OwnerThreadHandle);
+
+ M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M);
+
+ M.WaitForLock(RequestingThread, RequestingThreadHandle);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M))
+ {
+ M.Unlock();
+ }
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ long Timeout = (long)ThreadState.X3;
+
+ HThread Thread = Ns.Os.Handles.GetData<HThread>(ThreadHandle);
+
+ Mutex M = new Mutex(Process, MutexAddress, ThreadHandle);
+
+ M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M);
+
+ M.GiveUpLock(ThreadHandle);
+
+ CondVar Cv = new CondVar(Process, CondVarAddress, Timeout);
+
+ Cv = Ns.Os.CondVars.GetOrAdd(CondVarAddress, Cv);
+
+ Cv.WaitForSignal(Thread);
+
+ M.WaitForLock(Thread, ThreadHandle);
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ HThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (Ns.Os.CondVars.TryGetValue(CondVarAddress, out CondVar Cv))
+ {
+ Cv.SetSignal(CurrThread, Count);
+ }
+
+ ThreadState.X0 = (int)SvcResult.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Utilities/IdPool.cs b/Ryujinx.Core/OsHle/Utilities/IdPool.cs
new file mode 100644
index 00000000..a7e181fa
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Utilities/IdPool.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Utilities
+{
+ class IdPool
+ {
+ private HashSet<int> Ids;
+
+ private int CurrId;
+ private int MinId;
+ private int MaxId;
+
+ public IdPool(int Min, int Max)
+ {
+ Ids = new HashSet<int>();
+
+ CurrId = Min;
+ MinId = Min;
+ MaxId = Max;
+ }
+
+ public IdPool() : this(1, int.MaxValue) { }
+
+ public int GenerateId()
+ {
+ lock (Ids)
+ {
+ for (int Cnt = MinId; Cnt < MaxId; Cnt++)
+ {
+ if (Ids.Add(CurrId))
+ {
+ return CurrId;
+ }
+
+ if (CurrId++ == MaxId)
+ {
+ CurrId = MinId;
+ }
+ }
+
+ return -1;
+ }
+ }
+
+ public bool DeleteId(int Id)
+ {
+ lock (Ids)
+ {
+ return Ids.Remove(Id);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Utilities/IdPoolWithObj.cs b/Ryujinx.Core/OsHle/Utilities/IdPoolWithObj.cs
new file mode 100644
index 00000000..f0a339df
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Utilities/IdPoolWithObj.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.Core.OsHle.Utilities
+{
+ class IdPoolWithObj : IEnumerable<KeyValuePair<int, object>>
+ {
+ private IdPool Ids;
+
+ private ConcurrentDictionary<int, object> Objs;
+
+ public IdPoolWithObj()
+ {
+ Ids = new IdPool();
+
+ Objs = new ConcurrentDictionary<int, object>();
+ }
+
+ public int GenerateId(object Data)
+ {
+ int Id = Ids.GenerateId();
+
+ if (Id == -1 || !Objs.TryAdd(Id, Data))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return Id;
+ }
+
+ public bool ReplaceData(int Id, object Data)
+ {
+ if (Objs.ContainsKey(Id))
+ {
+ Objs[Id] = Data;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public T GetData<T>(int Id)
+ {
+ if (Objs.TryGetValue(Id, out object Data) && Data is T)
+ {
+ return (T)Data;
+ }
+
+ return default(T);
+ }
+
+ public void Delete(int Id)
+ {
+ if (Objs.TryRemove(Id, out object Obj))
+ {
+ if (Obj is IDisposable DisposableObj)
+ {
+ DisposableObj.Dispose();
+ }
+
+ Ids.DeleteId(Id);
+ }
+ }
+
+ IEnumerator<KeyValuePair<int, object>> IEnumerable<KeyValuePair<int, object>>.GetEnumerator()
+ {
+ return Objs.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Objs.GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Utilities/MemReader.cs b/Ryujinx.Core/OsHle/Utilities/MemReader.cs
new file mode 100644
index 00000000..fe92f68f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Utilities/MemReader.cs
@@ -0,0 +1,44 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Core.OsHle.Utilities
+{
+ class MemReader
+ {
+ private AMemory Memory;
+
+ public long Position { get; private set; }
+
+ public MemReader(AMemory Memory, long Position)
+ {
+ this.Memory = Memory;
+ this.Position = Position;
+ }
+
+ public byte ReadByte()
+ {
+ byte Value = Memory.ReadByte(Position);
+
+ Position++;
+
+ return Value;
+ }
+
+ public int ReadInt32()
+ {
+ int Value = Memory.ReadInt32(Position);
+
+ Position += 4;
+
+ return Value;
+ }
+
+ public long ReadInt64()
+ {
+ long Value = Memory.ReadInt64(Position);
+
+ Position += 8;
+
+ return Value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Utilities/MemWriter.cs b/Ryujinx.Core/OsHle/Utilities/MemWriter.cs
new file mode 100644
index 00000000..21b6a3b6
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Utilities/MemWriter.cs
@@ -0,0 +1,38 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Core.OsHle.Utilities
+{
+ class MemWriter
+ {
+ private AMemory Memory;
+
+ public long Position { get; private set; }
+
+ public MemWriter(AMemory Memory, long Position)
+ {
+ this.Memory = Memory;
+ this.Position = Position;
+ }
+
+ public void WriteByte(byte Value)
+ {
+ Memory.WriteByte(Position, Value);
+
+ Position++;
+ }
+
+ public void WriteInt32(int Value)
+ {
+ Memory.WriteInt32(Position, Value);
+
+ Position += 4;
+ }
+
+ public void WriteInt64(long Value)
+ {
+ Memory.WriteInt64(Position, Value);
+
+ Position += 8;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/Ryujinx.Core.csproj b/Ryujinx.Core/Ryujinx.Core.csproj
new file mode 100644
index 00000000..7d5ad718
--- /dev/null
+++ b/Ryujinx.Core/Ryujinx.Core.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\ChocolArm64\ChocolArm64.csproj" />
+ <ProjectReference Include="..\Ryujinx.Graphics\Ryujinx.Graphics.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Core/Switch.cs b/Ryujinx.Core/Switch.cs
new file mode 100644
index 00000000..2b6a9045
--- /dev/null
+++ b/Ryujinx.Core/Switch.cs
@@ -0,0 +1,75 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.OsHle;
+using Ryujinx.Graphics.Gal;
+using Ryujinx.Graphics.Gpu;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Core
+{
+ public class Switch : IDisposable
+ {
+ public IntPtr Ram {get; private set; }
+
+ internal NsGpu Gpu { get; private set; }
+ internal Horizon Os { get; private set; }
+ internal VirtualFs VFs { get; private set; }
+ internal Hid Hid { get; private set; }
+
+ public event EventHandler Finish;
+
+ public Switch(IGalRenderer Renderer)
+ {
+ Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize);
+
+ Gpu = new NsGpu(Renderer);
+ Os = new Horizon(this);
+ VFs = new VirtualFs();
+ Hid = new Hid(this);
+ }
+
+ public void FinalizeAllProcesses()
+ {
+ Os.FinalizeAllProcesses();
+ }
+
+ public void LoadCart(string ExeFsDir, string RomFsFile = null)
+ {
+ Os.LoadCart(ExeFsDir, RomFsFile);
+ }
+
+ public void LoadProgram(string FileName)
+ {
+ Os.LoadProgram(FileName);
+ }
+
+ public void SendControllerButtons(HidControllerID ControllerId,
+ HidControllerLayouts Layout,
+ HidControllerKeys Buttons,
+ JoystickPosition LeftJoystick,
+ JoystickPosition RightJoystick)
+ {
+ Hid.SendControllerButtons(ControllerId, Layout, Buttons, LeftJoystick, RightJoystick);
+ }
+
+ internal virtual void OnFinish(EventArgs e)
+ {
+ Finish?.Invoke(this, e);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ VFs.Dispose();
+ }
+
+ Marshal.FreeHGlobal(Ram);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/VirtualFs.cs b/Ryujinx.Core/VirtualFs.cs
new file mode 100644
index 00000000..23c7285c
--- /dev/null
+++ b/Ryujinx.Core/VirtualFs.cs
@@ -0,0 +1,70 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.Core
+{
+ class VirtualFs : IDisposable
+ {
+ private const string BasePath = "Fs";
+ private const string SavesPath = "Saves";
+ private const string SdCardPath = "SdCard";
+
+ public Stream RomFs { get; private set; }
+
+ public void LoadRomFs(string FileName)
+ {
+ RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
+ }
+
+ public string GetFullPath(string BasePath, string FileName)
+ {
+ if (FileName.StartsWith('/'))
+ {
+ FileName = FileName.Substring(1);
+ }
+
+ string FullPath = Path.GetFullPath(Path.Combine(BasePath, FileName));
+
+ if (!FullPath.StartsWith(GetBasePath()))
+ {
+ return null;
+ }
+
+ return FullPath;
+ }
+
+ public string GetSdCardPath() => MakeDirAndGetFullPath(SdCardPath);
+
+ public string GetGameSavesPath() => MakeDirAndGetFullPath(SavesPath);
+
+ private static string MakeDirAndGetFullPath(string Dir)
+ {
+ string FullPath = Path.Combine(GetBasePath(), Dir);
+
+ if (!Directory.Exists(FullPath))
+ {
+ Directory.CreateDirectory(FullPath);
+ }
+
+ return FullPath;
+ }
+
+ public static string GetBasePath()
+ {
+ return Path.Combine(Directory.GetCurrentDirectory(), BasePath);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ RomFs?.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file