Merge branch 'master' into aot
This commit is contained in:
commit
4316494ca0
20 changed files with 684 additions and 50 deletions
|
@ -12,7 +12,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
{
|
||||
_appletMapping = new Dictionary<AppletId, Type>
|
||||
{
|
||||
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) }
|
||||
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
|
||||
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
{
|
||||
event EventHandler AppletStateChanged;
|
||||
|
||||
ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData);
|
||||
ResultCode Start(AppletSession normalSession,
|
||||
AppletSession interactiveSession);
|
||||
|
||||
ResultCode GetResult();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
{
|
||||
private Horizon _system;
|
||||
|
||||
private AppletFifo<byte[]> _inputData;
|
||||
private AppletFifo<byte[]> _outputData;
|
||||
private AppletSession _normalSession;
|
||||
private AppletSession _interactiveSession;
|
||||
|
||||
public event EventHandler AppletStateChanged;
|
||||
|
||||
|
@ -19,13 +19,14 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
_system = system;
|
||||
}
|
||||
|
||||
public ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData)
|
||||
public ResultCode Start(AppletSession normalSession,
|
||||
AppletSession interactiveSession)
|
||||
{
|
||||
_inputData = inData;
|
||||
_outputData = outData;
|
||||
_normalSession = normalSession;
|
||||
_interactiveSession = interactiveSession;
|
||||
|
||||
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
||||
_outputData.Push(BuildResponse());
|
||||
_normalSession.Push(BuildResponse());
|
||||
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
internal class SoftwareKeyboardApplet : IApplet
|
||||
{
|
||||
private const string DEFAULT_NUMB = "1";
|
||||
private const string DEFAULT_TEXT = "Ryujinx";
|
||||
|
||||
private const int STANDARD_BUFFER_SIZE = 0x7D8;
|
||||
private const int INTERACTIVE_BUFFER_SIZE = 0x7D4;
|
||||
|
||||
private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized;
|
||||
|
||||
private AppletSession _normalSession;
|
||||
private AppletSession _interactiveSession;
|
||||
|
||||
private SoftwareKeyboardConfig _keyboardConfig;
|
||||
|
||||
private string _textValue = DEFAULT_TEXT;
|
||||
|
||||
public event EventHandler AppletStateChanged;
|
||||
|
||||
public SoftwareKeyboardApplet(Horizon system) { }
|
||||
|
||||
public ResultCode Start(AppletSession normalSession,
|
||||
AppletSession interactiveSession)
|
||||
{
|
||||
_normalSession = normalSession;
|
||||
_interactiveSession = interactiveSession;
|
||||
|
||||
_interactiveSession.DataAvailable += OnInteractiveData;
|
||||
|
||||
var launchParams = _normalSession.Pop();
|
||||
var keyboardConfig = _normalSession.Pop();
|
||||
var transferMemory = _normalSession.Pop();
|
||||
|
||||
_keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
|
||||
|
||||
_state = SoftwareKeyboardState.Ready;
|
||||
|
||||
Execute();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode GetResult()
|
||||
{
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
{
|
||||
// If the keyboard type is numbers only, we swap to a default
|
||||
// text that only contains numbers.
|
||||
if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly)
|
||||
{
|
||||
_textValue = DEFAULT_NUMB;
|
||||
}
|
||||
|
||||
// If the max string length is 0, we set it to a large default
|
||||
// length.
|
||||
if (_keyboardConfig.StringLengthMax == 0)
|
||||
{
|
||||
_keyboardConfig.StringLengthMax = 100;
|
||||
}
|
||||
|
||||
// If our default text is longer than the allowed length,
|
||||
// we truncate it.
|
||||
if (_textValue.Length > _keyboardConfig.StringLengthMax)
|
||||
{
|
||||
_textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax);
|
||||
}
|
||||
|
||||
if (!_keyboardConfig.CheckText)
|
||||
{
|
||||
// If the application doesn't need to validate the response,
|
||||
// we push the data to the non-interactive output buffer
|
||||
// and poll it for completion.
|
||||
_state = SoftwareKeyboardState.Complete;
|
||||
|
||||
_normalSession.Push(BuildResponse(_textValue, false));
|
||||
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The application needs to validate the response, so we
|
||||
// submit it to the interactive output buffer, and poll it
|
||||
// for validation. Once validated, the application will submit
|
||||
// back a validation status, which is handled in OnInteractiveDataPushIn.
|
||||
_state = SoftwareKeyboardState.ValidationPending;
|
||||
|
||||
_interactiveSession.Push(BuildResponse(_textValue, true));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractiveData(object sender, EventArgs e)
|
||||
{
|
||||
// Obtain the validation status response,
|
||||
var data = _interactiveSession.Pop();
|
||||
|
||||
if (_state == SoftwareKeyboardState.ValidationPending)
|
||||
{
|
||||
// TODO(jduncantor):
|
||||
// If application rejects our "attempt", submit another attempt,
|
||||
// and put the applet back in PendingValidation state.
|
||||
|
||||
// For now we assume success, so we push the final result
|
||||
// to the standard output buffer and carry on our merry way.
|
||||
_normalSession.Push(BuildResponse(_textValue, false));
|
||||
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
|
||||
_state = SoftwareKeyboardState.Complete;
|
||||
}
|
||||
else if(_state == SoftwareKeyboardState.Complete)
|
||||
{
|
||||
// If we have already completed, we push the result text
|
||||
// back on the output buffer and poll the application.
|
||||
_normalSession.Push(BuildResponse(_textValue, false));
|
||||
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We shouldn't be able to get here through standard swkbd execution.
|
||||
throw new InvalidOperationException("Software Keyboard is in an invalid state.");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] BuildResponse(string text, bool interactive)
|
||||
{
|
||||
int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE;
|
||||
|
||||
using (MemoryStream stream = new MemoryStream(new byte[bufferSize]))
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
byte[] output = Encoding.Unicode.GetBytes(text);
|
||||
|
||||
if (!interactive)
|
||||
{
|
||||
// Result Code
|
||||
writer.Write((uint)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In interactive mode, we write the length of the text
|
||||
// as a long, rather than a result code.
|
||||
writer.Write((long)output.Length);
|
||||
}
|
||||
|
||||
writer.Write(output);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static T ReadStruct<T>(byte[] data)
|
||||
where T : struct
|
||||
{
|
||||
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
// TODO(jduncanator): Define all fields
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
struct SoftwareKeyboardConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of keyboard.
|
||||
/// </summary>
|
||||
[FieldOffset(0x0)]
|
||||
public SoftwareKeyboardType Type;
|
||||
|
||||
/// <summary>
|
||||
/// When non-zero, specifies the max string length. When the input is too long, swkbd will stop accepting more input until text is deleted via the B button (Backspace).
|
||||
/// </summary>
|
||||
[FieldOffset(0x3AC)]
|
||||
public uint StringLengthMax;
|
||||
|
||||
/// <summary>
|
||||
/// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button.
|
||||
/// </summary>
|
||||
[FieldOffset(0x3B0)]
|
||||
public uint StringLengthMaxExtended;
|
||||
|
||||
/// <summary>
|
||||
/// When set, the application will validate the entered text whilst the swkbd is still on screen.
|
||||
/// </summary>
|
||||
[FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)]
|
||||
public bool CheckText;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
internal enum SoftwareKeyboardState
|
||||
{
|
||||
/// <summary>
|
||||
/// swkbd is uninitialized.
|
||||
/// </summary>
|
||||
Uninitialized,
|
||||
|
||||
/// <summary>
|
||||
/// swkbd is ready to process data.
|
||||
/// </summary>
|
||||
Ready,
|
||||
|
||||
/// <summary>
|
||||
/// swkbd is awaiting an interactive reply with a validation status.
|
||||
/// </summary>
|
||||
ValidationPending,
|
||||
|
||||
/// <summary>
|
||||
/// swkbd has completed.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
{
|
||||
internal enum SoftwareKeyboardType : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal keyboard.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText.
|
||||
/// </summary>
|
||||
NumbersOnly = 1,
|
||||
|
||||
/// <summary>
|
||||
/// QWERTY (and variants) keyboard only.
|
||||
/// </summary>
|
||||
LettersOnly = 2
|
||||
}
|
||||
}
|
|
@ -11,35 +11,50 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
{
|
||||
private IApplet _applet;
|
||||
|
||||
private AppletFifo<byte[]> _inData;
|
||||
private AppletFifo<byte[]> _outData;
|
||||
private AppletSession _normalSession;
|
||||
private AppletSession _interactiveSession;
|
||||
|
||||
private KEvent _stateChangedEvent;
|
||||
private KEvent _normalOutDataEvent;
|
||||
private KEvent _interactiveOutDataEvent;
|
||||
|
||||
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
|
||||
{
|
||||
_stateChangedEvent = new KEvent(system);
|
||||
_stateChangedEvent = new KEvent(system);
|
||||
_normalOutDataEvent = new KEvent(system);
|
||||
_interactiveOutDataEvent = new KEvent(system);
|
||||
|
||||
_applet = AppletManager.Create(appletId, system);
|
||||
_inData = new AppletFifo<byte[]>();
|
||||
_outData = new AppletFifo<byte[]>();
|
||||
|
||||
_applet.AppletStateChanged += OnAppletStateChanged;
|
||||
_applet = AppletManager.Create(appletId, system);
|
||||
|
||||
_normalSession = new AppletSession();
|
||||
_interactiveSession = new AppletSession();
|
||||
|
||||
_applet.AppletStateChanged += OnAppletStateChanged;
|
||||
_normalSession.DataAvailable += OnNormalOutData;
|
||||
_interactiveSession.DataAvailable += OnInteractiveOutData;
|
||||
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created.");
|
||||
}
|
||||
|
||||
private void OnAppletStateChanged(object sender, EventArgs e)
|
||||
{
|
||||
_stateChangedEvent.ReadableEvent.Signal();
|
||||
_stateChangedEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
private void OnNormalOutData(object sender, EventArgs e)
|
||||
{
|
||||
_normalOutDataEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
private void OnInteractiveOutData(object sender, EventArgs e)
|
||||
{
|
||||
_interactiveOutDataEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetAppletStateChangedEvent() -> handle<copy>
|
||||
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
|
||||
{
|
||||
_stateChangedEvent.ReadableEvent.Signal();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
|
@ -54,7 +69,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// Start()
|
||||
public ResultCode Start(ServiceCtx context)
|
||||
{
|
||||
return (ResultCode)_applet.Start(_inData, _outData);
|
||||
return (ResultCode)_applet.Start(_normalSession.GetConsumer(),
|
||||
_interactiveSession.GetConsumer());
|
||||
}
|
||||
|
||||
[Command(30)]
|
||||
|
@ -70,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
_inData.Push(data.Data);
|
||||
_normalSession.Push(data.Data);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -79,10 +95,70 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// PopOutData() -> object<nn::am::service::IStorage>
|
||||
public ResultCode PopOutData(ServiceCtx context)
|
||||
{
|
||||
byte[] data = _outData.Pop();
|
||||
if(_normalSession.TryPop(out byte[] data))
|
||||
{
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
_normalOutDataEvent.WritableEvent.Clear();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.NotAvailable;
|
||||
}
|
||||
|
||||
[Command(103)]
|
||||
// PushInteractiveInData(object<nn::am::service::IStorage>)
|
||||
public ResultCode PushInteractiveInData(ServiceCtx context)
|
||||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
_interactiveSession.Push(data.Data);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(104)]
|
||||
// PopInteractiveOutData() -> object<nn::am::service::IStorage>
|
||||
public ResultCode PopInteractiveOutData(ServiceCtx context)
|
||||
{
|
||||
if(_interactiveSession.TryPop(out byte[] data))
|
||||
{
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
_interactiveOutDataEvent.WritableEvent.Clear();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.NotAvailable;
|
||||
}
|
||||
|
||||
[Command(105)]
|
||||
// GetPopOutDataEvent() -> handle<copy>
|
||||
public ResultCode GetPopOutDataEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(106)]
|
||||
// GetPopInteractiveOutDataEvent() -> handle<copy>
|
||||
public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,5 +29,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(11)]
|
||||
// CreateTransferMemoryStorage(b8, u64, handle<copy>) -> object<nn::am::service::IStorage>
|
||||
public ResultCode CreateTransferMemoryStorage(ServiceCtx context)
|
||||
{
|
||||
bool unknown = context.RequestData.ReadBoolean();
|
||||
long size = context.RequestData.ReadInt64();
|
||||
|
||||
// NOTE: We don't support TransferMemory for now.
|
||||
|
||||
MakeObject(context, new IStorage(new byte[size]));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,26 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
internal class AppletFifo<T> : IEnumerable<T>
|
||||
internal class AppletFifo<T> : IAppletFifo<T>
|
||||
{
|
||||
private ConcurrentQueue<T> _dataQueue;
|
||||
|
||||
public int Count => _dataQueue.Count;
|
||||
public event EventHandler DataAvailable;
|
||||
|
||||
public bool IsSynchronized
|
||||
{
|
||||
get { return ((ICollection)_dataQueue).IsSynchronized; }
|
||||
}
|
||||
|
||||
public object SyncRoot
|
||||
{
|
||||
get { return ((ICollection)_dataQueue).SyncRoot; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _dataQueue.Count; }
|
||||
}
|
||||
|
||||
public AppletFifo()
|
||||
{
|
||||
|
@ -19,6 +34,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
public void Push(T item)
|
||||
{
|
||||
_dataQueue.Enqueue(item);
|
||||
|
||||
DataAvailable?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Push(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
|
@ -36,6 +67,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
return _dataQueue.TryDequeue(out result);
|
||||
}
|
||||
|
||||
public bool TryTake(out T item)
|
||||
{
|
||||
return this.TryPop(out item);
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (_dataQueue.TryPeek(out T result))
|
||||
|
@ -66,6 +102,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
_dataQueue.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
this.CopyTo((T[])array, index);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _dataQueue.GetEnumerator();
|
||||
|
|
77
Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs
Normal file
77
Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
internal class AppletSession
|
||||
{
|
||||
private IAppletFifo<byte[]> _inputData;
|
||||
private IAppletFifo<byte[]> _outputData;
|
||||
|
||||
public event EventHandler DataAvailable;
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return _inputData.Count; }
|
||||
}
|
||||
|
||||
public AppletSession()
|
||||
: this(new AppletFifo<byte[]>(),
|
||||
new AppletFifo<byte[]>())
|
||||
{ }
|
||||
|
||||
public AppletSession(
|
||||
IAppletFifo<byte[]> inputData,
|
||||
IAppletFifo<byte[]> outputData)
|
||||
{
|
||||
_inputData = inputData;
|
||||
_outputData = outputData;
|
||||
|
||||
_inputData.DataAvailable += OnDataAvailable;
|
||||
}
|
||||
|
||||
private void OnDataAvailable(object sender, EventArgs e)
|
||||
{
|
||||
DataAvailable?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public void Push(byte[] item)
|
||||
{
|
||||
if (!this.TryPush(item))
|
||||
{
|
||||
// TODO(jduncanator): Throw a proper exception
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPush(byte[] item)
|
||||
{
|
||||
return _outputData.TryAdd(item);
|
||||
}
|
||||
|
||||
public byte[] Pop()
|
||||
{
|
||||
if (this.TryPop(out byte[] item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Input data empty.");
|
||||
}
|
||||
|
||||
public bool TryPop(out byte[] item)
|
||||
{
|
||||
return _inputData.TryTake(out item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns an AppletSession that can be used at the
|
||||
/// other end of the pipe. Pushing data into this new session
|
||||
/// will put it in the first session's input buffer, and vice
|
||||
/// versa.
|
||||
/// </summary>
|
||||
public AppletSession GetConsumer()
|
||||
{
|
||||
return new AppletSession(this._outputData, this._inputData);
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs
Normal file
10
Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
interface IAppletFifo<T> : IProducerConsumerCollection<T>
|
||||
{
|
||||
event EventHandler DataAvailable;
|
||||
}
|
||||
}
|
|
@ -24,11 +24,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
// Write(u64, buffer<bytes, 0x21>)
|
||||
public ResultCode Write(ServiceCtx context)
|
||||
{
|
||||
// TODO: Error conditions.
|
||||
long writePosition = context.RequestData.ReadInt64();
|
||||
|
||||
if (writePosition > _storage.Data.Length)
|
||||
{
|
||||
return ResultCode.OutOfBounds;
|
||||
}
|
||||
|
||||
(long position, long size) = context.Request.GetBufferType0x21();
|
||||
|
||||
size = Math.Min(size, _storage.Data.Length - writePosition);
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
long maxSize = _storage.Data.Length - writePosition;
|
||||
|
@ -50,23 +56,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
// Read(u64) -> buffer<bytes, 0x22>
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
// TODO: Error conditions.
|
||||
long readPosition = context.RequestData.ReadInt64();
|
||||
|
||||
if (readPosition > _storage.Data.Length)
|
||||
{
|
||||
return ResultCode.OutOfBounds;
|
||||
}
|
||||
|
||||
(long position, long size) = context.Request.GetBufferType0x22();
|
||||
|
||||
byte[] data;
|
||||
size = Math.Min(size, _storage.Data.Length - readPosition);
|
||||
|
||||
if (_storage.Data.Length > size)
|
||||
{
|
||||
data = new byte[size];
|
||||
byte[] data = new byte[size];
|
||||
|
||||
Buffer.BlockCopy(_storage.Data, 0, data, 0, (int)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = _storage.Data;
|
||||
}
|
||||
Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size);
|
||||
|
||||
context.Memory.WriteBytes(position, data);
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ using Ryujinx.HLE.HOS.Kernel.Common;
|
|||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage;
|
||||
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy
|
||||
|
@ -31,13 +33,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
// EnsureSaveData(nn::account::Uid) -> u64
|
||||
public ResultCode EnsureSaveData(ServiceCtx context)
|
||||
{
|
||||
long uIdLow = context.RequestData.ReadInt64();
|
||||
long uIdHigh = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceAm);
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
|
||||
context.ResponseData.Write(0L);
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceAm, new { userId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -54,9 +55,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
// SetTerminateResult(u32)
|
||||
public ResultCode SetTerminateResult(ServiceCtx context)
|
||||
{
|
||||
int errorCode = context.RequestData.ReadInt32();
|
||||
|
||||
string result = GetFormattedErrorCode(errorCode);
|
||||
int errorCode = context.RequestData.ReadInt32();
|
||||
string result = GetFormattedErrorCode(errorCode);
|
||||
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"Result = 0x{errorCode:x8} ({result}).");
|
||||
|
||||
|
@ -95,10 +95,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
// GetPseudoDeviceId() -> nn::util::Uuid
|
||||
public ResultCode GetPseudoDeviceId(ServiceCtx context)
|
||||
{
|
||||
Logger.PrintStub(LogClass.ServiceAm);
|
||||
context.ResponseData.Write(0L);
|
||||
context.ResponseData.Write(0L);
|
||||
|
||||
context.ResponseData.Write(0L);
|
||||
context.ResponseData.Write(0L);
|
||||
Logger.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -118,11 +118,27 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
{
|
||||
int state = context.RequestData.ReadInt32();
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceAm);
|
||||
Logger.PrintStub(LogClass.ServiceAm, new { state });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(110)] // 5.0.0+
|
||||
// QueryApplicationPlayStatistics(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
|
||||
public ResultCode QueryApplicationPlayStatistics(ServiceCtx context)
|
||||
{
|
||||
// TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented.
|
||||
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context);
|
||||
}
|
||||
|
||||
[Command(111)] // 6.0.0+
|
||||
// QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
|
||||
public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context)
|
||||
{
|
||||
// TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented.
|
||||
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
|
||||
}
|
||||
|
||||
[Command(130)] // 8.0.0+
|
||||
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
|
||||
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
|
||||
|
|
|
@ -7,7 +7,10 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
|||
|
||||
Success = 0,
|
||||
|
||||
NotAvailable = (2 << ErrorCodeShift) | ModuleId,
|
||||
NoMessages = (3 << ErrorCodeShift) | ModuleId,
|
||||
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
|
||||
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
|
||||
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
|
@ -1,8 +1,24 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
|
||||
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
|
||||
{
|
||||
[Service("pdm:qry")]
|
||||
class IQueryService : IpcService
|
||||
{
|
||||
public IQueryService(ServiceCtx context) { }
|
||||
|
||||
[Command(13)] // 5.0.0+
|
||||
// QueryApplicationPlayStatisticsForSystem(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
|
||||
public ResultCode QueryApplicationPlayStatisticsForSystem(ServiceCtx context)
|
||||
{
|
||||
return QueryPlayStatisticsManager.GetPlayStatistics(context);
|
||||
}
|
||||
|
||||
[Command(16)] // 6.0.0+
|
||||
// QueryApplicationPlayStatisticsByUserAccountIdForSystem(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
|
||||
public ResultCode QueryApplicationPlayStatisticsByUserAccountIdForSystem(ServiceCtx context)
|
||||
{
|
||||
return QueryPlayStatisticsManager.GetPlayStatistics(context, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
||||
{
|
||||
static class QueryPlayStatisticsManager
|
||||
{
|
||||
private static Dictionary<UInt128, ApplicationPlayStatistics> applicationPlayStatistics = new Dictionary<UInt128, ApplicationPlayStatistics>();
|
||||
|
||||
internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
|
||||
{
|
||||
long inputPosition = context.Request.SendBuff[0].Position;
|
||||
long inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
long outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
long outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
UInt128 userId = byUserId ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
|
||||
|
||||
if (byUserId)
|
||||
{
|
||||
if (!context.Device.System.State.Account.TryGetUser(userId, out _))
|
||||
{
|
||||
return ResultCode.UserNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.PlayLogQueryCapability;
|
||||
|
||||
List<ulong> titleIds = new List<ulong>();
|
||||
|
||||
for (int i = 0; i < inputSize / sizeof(ulong); i++)
|
||||
{
|
||||
titleIds.Add(BitConverter.ToUInt64(context.Memory.ReadBytes(inputPosition, inputSize), 0));
|
||||
}
|
||||
|
||||
if (queryCapability == PlayLogQueryCapability.WhiteList)
|
||||
{
|
||||
// Check if input title ids are in the whitelist.
|
||||
foreach (ulong titleId in titleIds)
|
||||
{
|
||||
if (!context.Device.System.ControlData.PlayLogQueryableApplicationId.Contains(titleId))
|
||||
{
|
||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
|
||||
|
||||
// Return ResultCode.ServiceUnavailable if data is locked by another process.
|
||||
var filteredApplicationPlayStatistics = applicationPlayStatistics.AsEnumerable();
|
||||
|
||||
if (queryCapability == PlayLogQueryCapability.None)
|
||||
{
|
||||
filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Value.TitleId == context.Process.TitleId);
|
||||
}
|
||||
else // PlayLogQueryCapability.All
|
||||
{
|
||||
filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => titleIds.Contains(kv.Value.TitleId));
|
||||
}
|
||||
|
||||
if (byUserId)
|
||||
{
|
||||
filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Key == userId);
|
||||
}
|
||||
|
||||
for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++)
|
||||
{
|
||||
MemoryHelper.Write(context.Memory, outputPosition + (i * Marshal.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
|
||||
}
|
||||
|
||||
context.ResponseData.Write(filteredApplicationPlayStatistics.Count());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
|
||||
struct ApplicationPlayStatistics
|
||||
{
|
||||
public ulong TitleId;
|
||||
public long TotalPlayTime; // In nanoseconds.
|
||||
public long TotalLaunchCount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types
|
||||
{
|
||||
enum PlayLogQueryCapability
|
||||
{
|
||||
None,
|
||||
WhiteList,
|
||||
All
|
||||
}
|
||||
}
|
13
Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs
Normal file
13
Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm
|
||||
{
|
||||
enum ResultCode
|
||||
{
|
||||
ModuleId = 178,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
UserNotFound = (101 << ErrorCodeShift) | ModuleId,
|
||||
ServiceUnavailable = (150 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue