diff options
| author | emmauss <emmausssss@gmail.com> | 2018-02-20 22:09:23 +0200 |
|---|---|---|
| committer | gdkchan <gab.dark.100@gmail.com> | 2018-02-20 17:09:23 -0300 |
| commit | 62b827f474f0aa2152dd339fcc7cf31084e16a0b (patch) | |
| tree | 0e5c55b341aee4db0ccb841a084f253ec5e05657 /Ryujinx.Core | |
| parent | cb665bb715834526d73c9469d16114b287faaecd (diff) | |
Split main project into core,graphics and chocolarm4 subproject (#29)
Diffstat (limited to 'Ryujinx.Core')
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 |
