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

* Some style fixes and nits on ITimeZoneService

* Remove some unneeded usings

* Remove the Ryujinx.HLE.OsHle.Handles namespace

* Remove hbmenu automatic load on process exit

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

* Move Exceptions and Utilities out of OsHle

* Rename OsHle to HOS

* Rename OsHle folder to HOS

* IManagerDisplayService and ISystemDisplayService style fixes

* BsdError shouldn't be public

* Add a empty new line before using static

* Remove unused file

* Some style fixes on NPDM

* Exit gracefully when the application is closed

* Code style fixes on IGeneralService

* Add 0x prefix on values printed as hex

* Small improvements on finalization code

* Move ProcessId and ThreadId out of AThreadState

* Rename VFs to FileSystem

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

* More case changes on NPDM

* Remove unused files

* Move using to the correct place on NPDM

* Use properties on KernelAccessControlMmio

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

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Acc
{
static class AccErr
{
public const int UserNotFound = 100;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,83 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am
{
class IApplicationProxy : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IApplicationProxy()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetCommonStateGetter },
{ 1, GetSelfController },
{ 2, GetWindowController },
{ 3, GetAudioController },
{ 4, GetDisplayController },
{ 11, GetLibraryAppletCreator },
{ 20, GetApplicationFunctions },
{ 1000, GetDebugFunctions }
};
}
public long GetCommonStateGetter(ServiceCtx Context)
{
MakeObject(Context, new ICommonStateGetter());
return 0;
}
public long GetSelfController(ServiceCtx Context)
{
MakeObject(Context, new ISelfController());
return 0;
}
public long GetWindowController(ServiceCtx Context)
{
MakeObject(Context, new IWindowController());
return 0;
}
public long GetAudioController(ServiceCtx Context)
{
MakeObject(Context, new IAudioController());
return 0;
}
public long GetDisplayController(ServiceCtx Context)
{
MakeObject(Context, new IDisplayController());
return 0;
}
public long GetLibraryAppletCreator(ServiceCtx Context)
{
MakeObject(Context, new ILibraryAppletCreator());
return 0;
}
public long GetApplicationFunctions(ServiceCtx Context)
{
MakeObject(Context, new IApplicationFunctions());
return 0;
}
public long GetDebugFunctions(ServiceCtx Context)
{
MakeObject(Context, new IDebugFunctions());
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,145 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am
{
class ISelfController : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent LaunchableEvent;
public ISelfController()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Exit },
{ 1, LockExit },
{ 2, UnlockExit },
{ 9, GetLibraryAppletLaunchableEvent },
{ 10, SetScreenShotPermission },
{ 11, SetOperationModeChangedNotification },
{ 12, SetPerformanceModeChangedNotification },
{ 13, SetFocusHandlingMode },
{ 14, SetRestartMessageEnabled },
{ 16, SetOutOfFocusSuspendingEnabled },
{ 19, SetScreenShotImageOrientation },
{ 50, SetHandlesRequestToDisplay }
};
LaunchableEvent = new KEvent();
}
public long Exit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long LockExit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long UnlockExit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long GetLibraryAppletLaunchableEvent(ServiceCtx Context)
{
LaunchableEvent.WaitEvent.Set();
int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetScreenShotPermission(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOperationModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetPerformanceModeChangedNotification(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetFocusHandlingMode(ServiceCtx Context)
{
bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false;
bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false;
bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetRestartMessageEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetScreenShotImageOrientation(ServiceCtx Context)
{
int Orientation = Context.RequestData.ReadInt32();
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
public long SetHandlesRequestToDisplay(ServiceCtx Context)
{
bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
return 0;
}
}
}

View file

@ -0,0 +1,31 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am
{
class IStorage : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public byte[] Data { get; private set; }
public IStorage(byte[] Data)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Open }
};
this.Data = Data;
}
public long Open(ServiceCtx Context)
{
MakeObject(Context, new IStorageAccessor(this));
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,27 @@
using System.IO;
namespace Ryujinx.HLE.HOS.Services.Am
{
class StorageHelper
{
private const uint LaunchParamsMagic = 0xc79497ca;
public static byte[] MakeLaunchParams()
{
//Size needs to be at least 0x88 bytes otherwise application errors.
using (MemoryStream MS = new MemoryStream())
{
BinaryWriter Writer = new BinaryWriter(MS);
MS.SetLength(0x88);
Writer.Write(LaunchParamsMagic);
Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used.
Writer.Write(1L); //User Id Low (note: User Id needs to be != 0)
Writer.Write(0L); //User Id High
return MS.ToArray();
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Aud
{
enum SampleFormat : byte
{
Invalid = 0,
PcmInt8 = 1,
PcmInt16 = 2,
PcmInt24 = 3,
PcmInt32 = 4,
PcmFloat = 5,
Adpcm = 6
}
}

View file

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

View file

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

View file

@ -0,0 +1,39 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Bcat
{
class IServiceCreator : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IServiceCreator()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, CreateBcatService },
{ 1, CreateDeliveryCacheStorageService }
};
}
public long CreateBcatService(ServiceCtx Context)
{
long Id = Context.RequestData.ReadInt64();
MakeObject(Context, new IBcatService());
return 0;
}
public long CreateDeliveryCacheStorageService(ServiceCtx Context)
{
long Id = Context.RequestData.ReadInt64();
MakeObject(Context, new IDeliveryCacheStorageService());
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,49 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Friend
{
class IFriendService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IFriendService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 10601, DeclareCloseOnlinePlaySession },
{ 10610, UpdateUserPresence }
};
}
public long DeclareCloseOnlinePlaySession(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
if (Context.Device.System.State.TryGetUser(Uuid, out UserProfile Profile))
{
Profile.OnlinePlayState = OpenCloseState.Closed;
}
return 0;
}
public long UpdateUserPresence(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
//TODO.
Context.Device.Log.PrintStub(LogClass.ServiceFriend, "Stubbed.");
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,51 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.HOS.Services.FspSrv
{
class IStorage : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private Stream BaseStream;
public IStorage(Stream BaseStream)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Read }
};
this.BaseStream = BaseStream;
}
public long Read(ServiceCtx Context)
{
long Offset = Context.RequestData.ReadInt64();
long Size = Context.RequestData.ReadInt64();
if (Context.Request.ReceiveBuff.Count > 0)
{
IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0];
//Use smaller length to avoid overflows.
if (Size > BuffDesc.Size)
{
Size = BuffDesc.Size;
}
byte[] Data = new byte[Size];
BaseStream.Seek(Offset, SeekOrigin.Begin);
BaseStream.Read(Data, 0, Data.Length);
Context.Memory.WriteBytes(BuffDesc.Position, Data);
}
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Nfp
{
enum DeviceState
{
Initialized = 0
}
}

View file

@ -0,0 +1,114 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Input;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nfp
{
class IUser : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private const HidControllerId NpadId = HidControllerId.CONTROLLER_PLAYER_1;
private State State = State.NonInitialized;
private DeviceState DeviceState = DeviceState.Initialized;
private KEvent ActivateEvent;
private KEvent DeactivateEvent;
private KEvent AvailabilityChangeEvent;
public IUser()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Initialize },
{ 17, AttachActivateEvent },
{ 18, AttachDeactivateEvent },
{ 19, GetState },
{ 20, GetDeviceState },
{ 21, GetNpadId },
{ 23, AttachAvailabilityChangeEvent }
};
ActivateEvent = new KEvent();
DeactivateEvent = new KEvent();
AvailabilityChangeEvent = new KEvent();
}
public long Initialize(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
State = State.Initialized;
return 0;
}
public long AttachActivateEvent(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
int Handle = Context.Process.HandleTable.OpenHandle(ActivateEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);;
return 0;
}
public long AttachDeactivateEvent(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
int Handle = Context.Process.HandleTable.OpenHandle(DeactivateEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
public long GetState(ServiceCtx Context)
{
Context.ResponseData.Write((int)State);
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
return 0;
}
public long GetDeviceState(ServiceCtx Context)
{
Context.ResponseData.Write((int)DeviceState);
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
return 0;
}
public long GetNpadId(ServiceCtx Context)
{
Context.ResponseData.Write((int)NpadId);
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
return 0;
}
public long AttachAvailabilityChangeEvent(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
int Handle = Context.Process.HandleTable.OpenHandle(AvailabilityChangeEvent);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
return 0;
}
}
}

View file

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

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Nfp
{
enum State
{
NonInitialized = 0,
Initialized = 1
}
}

View file

@ -0,0 +1,58 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Services.Nifm
{
class IGeneralService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IGeneralService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, CreateRequest },
{ 12, GetCurrentIpAddress }
};
}
public long CreateRequest(ServiceCtx Context)
{
int Unknown = Context.RequestData.ReadInt32();
MakeObject(Context, new IRequest());
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public long GetCurrentIpAddress(ServiceCtx Context)
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return MakeError(ErrorModule.Nifm, NifmErr.NoInternetConnection);
}
IPHostEntry Host = Dns.GetHostEntry(Dns.GetHostName());
IPAddress Address = Host.AddressList.FirstOrDefault(A => A.AddressFamily == AddressFamily.InterNetwork);
Context.ResponseData.Write(BitConverter.ToUInt32(Address.GetAddressBytes()));
Context.Device.Log.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is \"{Address}\".");
return 0;
}
}
}

View file

@ -0,0 +1,95 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nifm
{
class IRequest : IpcService, IDisposable
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private KEvent Event0;
private KEvent Event1;
public IRequest()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, GetRequestState },
{ 1, GetResult },
{ 2, GetSystemEventReadableHandles },
{ 3, Cancel },
{ 4, Submit },
{ 11, SetConnectionConfirmationOption }
};
Event0 = new KEvent();
Event1 = new KEvent();
}
public long GetRequestState(ServiceCtx Context)
{
Context.ResponseData.Write(1);
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public long GetResult(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public long GetSystemEventReadableHandles(ServiceCtx Context)
{
int Handle0 = Context.Process.HandleTable.OpenHandle(Event0);
int Handle1 = Context.Process.HandleTable.OpenHandle(Event1);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle0, Handle1);
return 0;
}
public long Cancel(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public long Submit(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public long SetConnectionConfirmationOption(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
return 0;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
Event0.Dispose();
Event1.Dispose();
}
}
}
}

View file

@ -0,0 +1,35 @@
using Ryujinx.HLE.HOS.Ipc;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nifm
{
class IStaticService : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IStaticService()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 4, CreateGeneralServiceOld },
{ 5, CreateGeneralService }
};
}
public long CreateGeneralServiceOld(ServiceCtx Context)
{
MakeObject(Context, new IGeneralService());
return 0;
}
public long CreateGeneralService(ServiceCtx Context)
{
MakeObject(Context, new IGeneralService());
return 0;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Nifm
{
static class NifmErr
{
public const int NoInternetConnection = 300;
}
}

View file

@ -0,0 +1,42 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.Logging;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Ns
{
class IAddOnContentManager : IpcService
{
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IAddOnContentManager()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 2, CountAddOnContent },
{ 3, ListAddOnContent }
};
}
public static long CountAddOnContent(ServiceCtx Context)
{
Context.ResponseData.Write(0);
Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed.");
return 0;
}
public static long ListAddOnContent(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed.");
//TODO: This is supposed to write a u32 array aswell.
//It's unknown what it contains.
Context.ResponseData.Write(0);
return 0;
}
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,231 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS;
using Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu;
using Ryujinx.HLE.HOS.Services.Nv.NvHostChannel;
using Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Nv.NvMap;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nv
{
class INvDrvServices : IpcService, IDisposable
{
private delegate int IoctlProcessor(ServiceCtx Context, int Cmd);
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
private static Dictionary<string, IoctlProcessor> IoctlProcessors =
new Dictionary<string, IoctlProcessor>()
{
{ "/dev/nvhost-as-gpu", ProcessIoctlNvGpuAS },
{ "/dev/nvhost-ctrl", ProcessIoctlNvHostCtrl },
{ "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu },
{ "/dev/nvhost-gpu", ProcessIoctlNvHostGpu },
{ "/dev/nvmap", ProcessIoctlNvMap }
};
public static GlobalStateTable Fds { get; private set; }
private KEvent Event;
public INvDrvServices()
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Open },
{ 1, Ioctl },
{ 2, Close },
{ 3, Initialize },
{ 4, QueryEvent },
{ 8, SetClientPid },
{ 11, Ioctl },
{ 13, FinishInitialize }
};
Event = new KEvent();
}
static INvDrvServices()
{
Fds = new GlobalStateTable();
}
public long Open(ServiceCtx Context)
{
long NamePtr = Context.Request.SendBuff[0].Position;
string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr);
int Fd = Fds.Add(Context.Process, new NvFd(Name));
Context.ResponseData.Write(Fd);
Context.ResponseData.Write(0);
return 0;
}
public long Ioctl(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
int Cmd = Context.RequestData.ReadInt32();
NvFd FdData = Fds.GetData<NvFd>(Context.Process, Fd);
int Result;
if (IoctlProcessors.TryGetValue(FdData.Name, out IoctlProcessor Process))
{
Result = Process(Context, Cmd);
}
else
{
throw new NotImplementedException($"{FdData.Name} {Cmd:x4}");
}
//TODO: Verify if the error codes needs to be translated.
Context.ResponseData.Write(Result);
return 0;
}
public long Close(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
Fds.Delete(Context.Process, Fd);
Context.ResponseData.Write(0);
return 0;
}
public long Initialize(ServiceCtx Context)
{
long TransferMemSize = Context.RequestData.ReadInt64();
int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0];
NvMapIoctl.InitializeNvMap(Context);
Context.ResponseData.Write(0);
return 0;
}
public long QueryEvent(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
int EventId = Context.RequestData.ReadInt32();
//TODO: Use Fd/EventId, different channels have different events.
int Handle = Context.Process.HandleTable.OpenHandle(Event);
Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
Context.ResponseData.Write(0);
return 0;
}
public long SetClientPid(ServiceCtx Context)
{
long Pid = Context.RequestData.ReadInt64();
Context.ResponseData.Write(0);
return 0;
}
public long FinishInitialize(ServiceCtx Context)
{
Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
return 0;
}
private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd)
{
return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl);
}
private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd)
{
return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl);
}
private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd)
{
return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl);
}
private static int ProcessIoctlNvHostGpu(ServiceCtx Context, int Cmd)
{
return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctlGpu);
}
private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd)
{
return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl);
}
private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor)
{
if (CmdIn(Cmd) && Context.Request.GetBufferType0x21().Position == 0)
{
Context.Device.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!");
return NvResult.InvalidInput;
}
if (CmdOut(Cmd) && Context.Request.GetBufferType0x22().Position == 0)
{
Context.Device.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!");
return NvResult.InvalidInput;
}
return Processor(Context, Cmd);
}
private static bool CmdIn(int Cmd)
{
return ((Cmd >> 30) & 1) != 0;
}
private static bool CmdOut(int Cmd)
{
return ((Cmd >> 31) & 1) != 0;
}
public static void UnloadProcess(Process Process)
{
Fds.DeleteProcess(Process);
NvGpuASIoctl.UnloadProcess(Process);
NvHostChannelIoctl.UnloadProcess(Process);
NvHostCtrlIoctl.UnloadProcess(Process);
NvMapIoctl.UnloadProcess(Process);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
if (Disposing)
{
Event.Dispose();
}
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Nv
{
class NvFd
{
public string Name { get; private set; }
public NvFd(string Name)
{
this.Name = Name;
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
{
struct NvGpuASAllocSpace
{
public int Pages;
public int PageSize;
public int Flags;
public int Padding;
public long Offset;
}
}

View file

@ -0,0 +1,197 @@
using Ryujinx.HLE.Gpu.Memory;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
{
class NvGpuASCtx
{
public NvGpuVmm Vmm { get; private set; }
private class Range
{
public ulong Start { get; private set; }
public ulong End { get; private set; }
public Range(long Position, long Size)
{
Start = (ulong)Position;
End = (ulong)Size + Start;
}
}
private class MappedMemory : Range
{
public long PhysicalAddress { get; private set; }
public bool VaAllocated { get; private set; }
public MappedMemory(
long Position,
long Size,
long PhysicalAddress,
bool VaAllocated) : base(Position, Size)
{
this.PhysicalAddress = PhysicalAddress;
this.VaAllocated = VaAllocated;
}
}
private SortedList<long, Range> Maps;
private SortedList<long, Range> Reservations;
public NvGpuASCtx(ServiceCtx Context)
{
Vmm = new NvGpuVmm(Context.Memory);
Maps = new SortedList<long, Range>();
Reservations = new SortedList<long, Range>();
}
public bool ValidateFixedBuffer(long Position, long Size)
{
long MapEnd = Position + Size;
//Check if size is valid (0 is also not allowed).
if ((ulong)MapEnd <= (ulong)Position)
{
return false;
}
//Check if address is page aligned.
if ((Position & NvGpuVmm.PageMask) != 0)
{
return false;
}
//Check if region is reserved.
if (BinarySearch(Reservations, Position) == null)
{
return false;
}
//Check for overlap with already mapped buffers.
Range Map = BinarySearchLt(Maps, MapEnd);
if (Map != null && Map.End > (ulong)Position)
{
return false;
}
return true;
}
public void AddMap(
long Position,
long Size,
long PhysicalAddress,
bool VaAllocated)
{
Maps.Add(Position, new MappedMemory(Position, Size, PhysicalAddress, VaAllocated));
}
public bool RemoveMap(long Position, out long Size)
{
Size = 0;
if (Maps.Remove(Position, out Range Value))
{
MappedMemory Map = (MappedMemory)Value;
if (Map.VaAllocated)
{
Size = (long)(Map.End - Map.Start);
}
return true;
}
return false;
}
public bool TryGetMapPhysicalAddress(long Position, out long PhysicalAddress)
{
Range Map = BinarySearch(Maps, Position);
if (Map != null)
{
PhysicalAddress = ((MappedMemory)Map).PhysicalAddress;
return true;
}
PhysicalAddress = 0;
return false;
}
public void AddReservation(long Position, long Size)
{
Reservations.Add(Position, new Range(Position, Size));
}
public bool RemoveReservation(long Position)
{
return Reservations.Remove(Position);
}
private Range BinarySearch(SortedList<long, Range> Lst, long Position)
{
int Left = 0;
int Right = Lst.Count - 1;
while (Left <= Right)
{
int Size = Right - Left;
int Middle = Left + (Size >> 1);
Range Rg = Lst.Values[Middle];
if ((ulong)Position >= Rg.Start && (ulong)Position < Rg.End)
{
return Rg;
}
if ((ulong)Position < Rg.Start)
{
Right = Middle - 1;
}
else
{
Left = Middle + 1;
}
}
return null;
}
private Range BinarySearchLt(SortedList<long, Range> Lst, long Position)
{
Range LtRg = null;
int Left = 0;
int Right = Lst.Count - 1;
while (Left <= Right)
{
int Size = Right - Left;
int Middle = Left + (Size >> 1);
Range Rg = Lst.Values[Middle];
if ((ulong)Position < Rg.Start)
{
Right = Middle - 1;
}
else
{
Left = Middle + 1;
LtRg = Rg;
}
}
return LtRg;
}
}
}

View file

@ -0,0 +1,329 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.Gpu.Memory;
using Ryujinx.HLE.HOS.Services.Nv.NvMap;
using Ryujinx.HLE.Logging;
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
{
class NvGpuASIoctl
{
private const int FlagFixedOffset = 1;
private const int FlagRemapSubRange = 0x100;
private static ConcurrentDictionary<Process, NvGpuASCtx> ASCtxs;
static NvGpuASIoctl()
{
ASCtxs = new ConcurrentDictionary<Process, NvGpuASCtx>();
}
public static int ProcessIoctl(ServiceCtx Context, int Cmd)
{
switch (Cmd & 0xffff)
{
case 0x4101: return BindChannel (Context);
case 0x4102: return AllocSpace (Context);
case 0x4103: return FreeSpace (Context);
case 0x4105: return UnmapBuffer (Context);
case 0x4106: return MapBufferEx (Context);
case 0x4108: return GetVaRegions(Context);
case 0x4109: return InitializeEx(Context);
case 0x4114: return Remap (Context, Cmd);
}
throw new NotImplementedException(Cmd.ToString("x8"));
}
private static int BindChannel(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
return NvResult.Success;
}
private static int AllocSpace(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
NvGpuASCtx ASCtx = GetASCtx(Context);
ulong Size = (ulong)Args.Pages *
(ulong)Args.PageSize;
int Result = NvResult.Success;
lock (ASCtx)
{
//Note: When the fixed offset flag is not set,
//the Offset field holds the alignment size instead.
if ((Args.Flags & FlagFixedOffset) != 0)
{
Args.Offset = ASCtx.Vmm.ReserveFixed(Args.Offset, (long)Size);
}
else
{
Args.Offset = ASCtx.Vmm.Reserve((long)Size, Args.Offset);
}
if (Args.Offset < 0)
{
Args.Offset = 0;
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {Size:x16}!");
Result = NvResult.OutOfMemory;
}
else
{
ASCtx.AddReservation(Args.Offset, (long)Size);
}
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return Result;
}
private static int FreeSpace(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
NvGpuASCtx ASCtx = GetASCtx(Context);
int Result = NvResult.Success;
lock (ASCtx)
{
ulong Size = (ulong)Args.Pages *
(ulong)Args.PageSize;
if (ASCtx.RemoveReservation(Args.Offset))
{
ASCtx.Vmm.Free(Args.Offset, (long)Size);
}
else
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv,
$"Failed to free offset 0x{Args.Offset:x16} size 0x{Size:x16}!");
Result = NvResult.InvalidInput;
}
}
return Result;
}
private static int UnmapBuffer(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
NvGpuASUnmapBuffer Args = AMemoryHelper.Read<NvGpuASUnmapBuffer>(Context.Memory, InputPosition);
NvGpuASCtx ASCtx = GetASCtx(Context);
lock (ASCtx)
{
if (ASCtx.RemoveMap(Args.Offset, out long Size))
{
if (Size != 0)
{
ASCtx.Vmm.Free(Args.Offset, Size);
}
}
else
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!");
}
}
return NvResult.Success;
}
private static int MapBufferEx(ServiceCtx Context)
{
const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!";
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
NvGpuASMapBufferEx Args = AMemoryHelper.Read<NvGpuASMapBufferEx>(Context.Memory, InputPosition);
NvGpuASCtx ASCtx = GetASCtx(Context);
NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
if (Map == null)
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
return NvResult.InvalidInput;
}
long PA;
if ((Args.Flags & FlagRemapSubRange) != 0)
{
lock (ASCtx)
{
if (ASCtx.TryGetMapPhysicalAddress(Args.Offset, out PA))
{
long VA = Args.Offset + Args.BufferOffset;
PA += Args.BufferOffset;
if (ASCtx.Vmm.Map(PA, VA, Args.MappingSize) < 0)
{
string Msg = string.Format(MapErrorMsg, VA, Args.MappingSize);
Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg);
return NvResult.InvalidInput;
}
return NvResult.Success;
}
else
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Address 0x{Args.Offset:x16} not mapped!");
return NvResult.InvalidInput;
}
}
}
PA = Map.Address + Args.BufferOffset;
long Size = Args.MappingSize;
if (Size == 0)
{
Size = (uint)Map.Size;
}
int Result = NvResult.Success;
lock (ASCtx)
{
//Note: When the fixed offset flag is not set,
//the Offset field holds the alignment size instead.
bool VaAllocated = (Args.Flags & FlagFixedOffset) == 0;
if (!VaAllocated)
{
if (ASCtx.ValidateFixedBuffer(Args.Offset, Size))
{
Args.Offset = ASCtx.Vmm.Map(PA, Args.Offset, Size);
}
else
{
string Msg = string.Format(MapErrorMsg, Args.Offset, Size);
Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg);
Result = NvResult.InvalidInput;
}
}
else
{
Args.Offset = ASCtx.Vmm.Map(PA, Size);
}
if (Args.Offset < 0)
{
Args.Offset = 0;
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{Size:x16}!");
Result = NvResult.InvalidInput;
}
else
{
ASCtx.AddMap(Args.Offset, Size, PA, VaAllocated);
}
}
AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
return Result;
}
private static int GetVaRegions(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
return NvResult.Success;
}
private static int InitializeEx(ServiceCtx Context)
{
long InputPosition = Context.Request.GetBufferType0x21().Position;
long OutputPosition = Context.Request.GetBufferType0x22().Position;
Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
return NvResult.Success;
}
private static int Remap(ServiceCtx Context, int Cmd)
{
int Count = ((Cmd >> 16) & 0xff) / 0x14;
long InputPosition = Context.Request.GetBufferType0x21().Position;
for (int Index = 0; Index < Count; Index++, InputPosition += 0x14)
{
NvGpuASRemap Args = AMemoryHelper.Read<NvGpuASRemap>(Context.Memory, InputPosition);
NvGpuVmm Vmm = GetASCtx(Context).Vmm;
NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
if (Map == null)
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
return NvResult.InvalidInput;
}
long Result = Vmm.Map(Map.Address, (long)(uint)Args.Offset << 16,
(long)(uint)Args.Pages << 16);
if (Result < 0)
{
Context.Device.Log.PrintWarning(LogClass.ServiceNv,
$"Page 0x{Args.Offset:x16} size 0x{Args.Pages:x16} not allocated!");
return NvResult.InvalidInput;
}
}
return NvResult.Success;
}
public static NvGpuASCtx GetASCtx(ServiceCtx Context)
{
return ASCtxs.GetOrAdd(Context.Process, (Key) => new NvGpuASCtx(Context));
}
public static void UnloadProcess(Process Process)
{
ASCtxs.TryRemove(Process, out _);
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
{
struct NvGpuASMapBufferEx
{
public int Flags;
public int Kind;
public int NvMapHandle;
public int PageSize;
public long BufferOffset;
public long MappingSize;
public long Offset;
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
{
struct NvGpuASRemap
{
public short Flags;
public short Kind;
public int NvMapHandle;
public int Padding;
public int Offset;
public int Pages;
}
}

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