am: Initial swkbd implementation
Currently only implements the full screen keyboard, inline keyboard will come later.
This commit is contained in:
parent
79abc6ed93
commit
6a107e6dfb
14 changed files with 476 additions and 35 deletions
|
@ -12,7 +12,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
_appletMapping = new Dictionary<AppletId, Type>
|
_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;
|
event EventHandler AppletStateChanged;
|
||||||
|
|
||||||
ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData);
|
ResultCode Start(AppletSession normalSession,
|
||||||
|
AppletSession interactiveSession);
|
||||||
|
|
||||||
ResultCode GetResult();
|
ResultCode GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
private Horizon _system;
|
private Horizon _system;
|
||||||
|
|
||||||
private AppletFifo<byte[]> _inputData;
|
private AppletSession _normalSession;
|
||||||
private AppletFifo<byte[]> _outputData;
|
private AppletSession _interactiveSession;
|
||||||
|
|
||||||
public event EventHandler AppletStateChanged;
|
public event EventHandler AppletStateChanged;
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||||
_system = system;
|
_system = system;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData)
|
public ResultCode Start(AppletSession normalSession,
|
||||||
|
AppletSession interactiveSession)
|
||||||
{
|
{
|
||||||
_inputData = inData;
|
_normalSession = normalSession;
|
||||||
_outputData = outData;
|
_interactiveSession = interactiveSession;
|
||||||
|
|
||||||
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
||||||
_outputData.Push(BuildResponse());
|
normalSession.Push(BuildResponse());
|
||||||
|
|
||||||
AppletStateChanged?.Invoke(this, null);
|
AppletStateChanged?.Invoke(this, null);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
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 (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly)
|
||||||
|
{
|
||||||
|
_textValue = DEFAULT_NUMB;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if(_state == SoftwareKeyboardState.ValidationPending)
|
||||||
|
{
|
||||||
|
// TODO(jduncantor):
|
||||||
|
// If application rejects our "attempt", submit another attempt,
|
||||||
|
// and put the applet back in PendingValidation state.
|
||||||
|
|
||||||
|
// Obtain the validation status response, for now we assume
|
||||||
|
// success and carry on our merry way.
|
||||||
|
_interactiveSession.Pop();
|
||||||
|
|
||||||
|
_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
|
||||||
|
{
|
||||||
|
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,20 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
struct SoftwareKeyboardConfig
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public SoftwareKeyboardType Type;
|
||||||
|
|
||||||
|
[FieldOffset(0x3AC)]
|
||||||
|
public uint StringLengthMax;
|
||||||
|
|
||||||
|
[FieldOffset(0x3B0)]
|
||||||
|
public uint StringLengthMaxExtended;
|
||||||
|
|
||||||
|
[FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool CheckText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
internal enum SoftwareKeyboardState
|
||||||
|
{
|
||||||
|
Uninitialized,
|
||||||
|
Ready,
|
||||||
|
ValidationPending,
|
||||||
|
Complete
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
|
{
|
||||||
|
internal enum SoftwareKeyboardType : uint
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
NumbersOnly = 1,
|
||||||
|
LettersOnly = 2
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,35 +11,50 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
{
|
{
|
||||||
private IApplet _applet;
|
private IApplet _applet;
|
||||||
|
|
||||||
private AppletFifo<byte[]> _inData;
|
private AppletSession _normalSession;
|
||||||
private AppletFifo<byte[]> _outData;
|
private AppletSession _interactiveSession;
|
||||||
|
|
||||||
private KEvent _stateChangedEvent;
|
private KEvent _stateChangedEvent;
|
||||||
|
private KEvent _normalOutDataEvent;
|
||||||
|
private KEvent _interactiveOutDataEvent;
|
||||||
|
|
||||||
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
|
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);
|
_applet = AppletManager.Create(appletId, system);
|
||||||
_inData = new AppletFifo<byte[]>();
|
|
||||||
_outData = new AppletFifo<byte[]>();
|
_normalSession = new AppletSession();
|
||||||
|
_interactiveSession = new AppletSession();
|
||||||
|
|
||||||
_applet.AppletStateChanged += OnAppletStateChanged;
|
_applet.AppletStateChanged += OnAppletStateChanged;
|
||||||
|
_normalSession.DataAvailable += OnNormalOutData;
|
||||||
|
_interactiveSession.DataAvailable += OnInteractiveOutData;
|
||||||
|
|
||||||
Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created.");
|
Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAppletStateChanged(object sender, EventArgs e)
|
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)]
|
[Command(0)]
|
||||||
// GetAppletStateChangedEvent() -> handle<copy>
|
// GetAppletStateChangedEvent() -> handle<copy>
|
||||||
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
|
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
|
||||||
{
|
{
|
||||||
_stateChangedEvent.ReadableEvent.Signal();
|
|
||||||
|
|
||||||
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Out of handles!");
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
@ -47,6 +62,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"GetAppletStateChangedEvent called.");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,13 +71,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
// Start()
|
// Start()
|
||||||
public ResultCode Start(ServiceCtx context)
|
public ResultCode Start(ServiceCtx context)
|
||||||
{
|
{
|
||||||
return (ResultCode)_applet.Start(_inData, _outData);
|
Logger.PrintInfo(LogClass.ServiceAm, $"Start called.");
|
||||||
|
|
||||||
|
return (ResultCode)_applet.Start(_normalSession.GetConsumer(),
|
||||||
|
_interactiveSession.GetConsumer());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command(30)]
|
[Command(30)]
|
||||||
// GetResult()
|
// GetResult()
|
||||||
public ResultCode GetResult(ServiceCtx context)
|
public ResultCode GetResult(ServiceCtx context)
|
||||||
{
|
{
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"GetResult called.");
|
||||||
|
|
||||||
return (ResultCode)_applet.GetResult();
|
return (ResultCode)_applet.GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +92,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
{
|
{
|
||||||
IStorage data = GetObject<IStorage>(context, 0);
|
IStorage data = GetObject<IStorage>(context, 0);
|
||||||
|
|
||||||
_inData.Push(data.Data);
|
_normalSession.Push(data.Data);
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"PushInData called.");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -79,10 +103,74 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||||
// PopOutData() -> object<nn::am::service::IStorage>
|
// PopOutData() -> object<nn::am::service::IStorage>
|
||||||
public ResultCode PopOutData(ServiceCtx context)
|
public ResultCode PopOutData(ServiceCtx context)
|
||||||
{
|
{
|
||||||
byte[] data = _outData.Pop();
|
byte[] data = _normalSession.Pop();
|
||||||
|
|
||||||
MakeObject(context, new IStorage(data));
|
MakeObject(context, new IStorage(data));
|
||||||
|
|
||||||
|
_normalOutDataEvent.WritableEvent.Clear();
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"PopOutData called.");
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(103)]
|
||||||
|
// PushInteractiveInData(object<nn::am::service::IStorage>)
|
||||||
|
public ResultCode PushInteractiveInData(ServiceCtx context)
|
||||||
|
{
|
||||||
|
IStorage data = GetObject<IStorage>(context, 0);
|
||||||
|
|
||||||
|
_interactiveSession.Push(data.Data);
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"PushInteractiveInData called.");
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command(104)]
|
||||||
|
// PopInteractiveOutData() -> object<nn::am::service::IStorage>
|
||||||
|
public ResultCode PopInteractiveOutData(ServiceCtx context)
|
||||||
|
{
|
||||||
|
byte[] data = _interactiveSession.Pop();
|
||||||
|
|
||||||
|
MakeObject(context, new IStorage(data));
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"PopInteractiveOutData called.");
|
||||||
|
|
||||||
|
_interactiveOutDataEvent.WritableEvent.Clear();
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"GetPopOutDataEvent called.");
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.ServiceAm, $"GetPopInteractiveOutDataEvent called.");
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||||
|
|
||||||
return ResultCode.Success;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,14 +2,30 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
{
|
{
|
||||||
internal class AppletFifo<T> : IEnumerable<T>
|
internal class AppletFifo<T> : IAppletFifo<T>
|
||||||
{
|
{
|
||||||
private ConcurrentQueue<T> _dataQueue;
|
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()
|
public AppletFifo()
|
||||||
{
|
{
|
||||||
|
@ -19,6 +35,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
public void Push(T item)
|
public void Push(T item)
|
||||||
{
|
{
|
||||||
_dataQueue.Enqueue(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()
|
public T Pop()
|
||||||
|
@ -36,6 +68,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
return _dataQueue.TryDequeue(out result);
|
return _dataQueue.TryDequeue(out result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryTake(out T item)
|
||||||
|
{
|
||||||
|
return this.TryPop(out item);
|
||||||
|
}
|
||||||
|
|
||||||
public T Peek()
|
public T Peek()
|
||||||
{
|
{
|
||||||
if (_dataQueue.TryPeek(out T result))
|
if (_dataQueue.TryPeek(out T result))
|
||||||
|
@ -66,6 +103,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
_dataQueue.CopyTo(array, arrayIndex);
|
_dataQueue.CopyTo(array, arrayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
this.CopyTo((T[])array, index);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
{
|
{
|
||||||
return _dataQueue.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>)
|
// Write(u64, buffer<bytes, 0x21>)
|
||||||
public ResultCode Write(ServiceCtx context)
|
public ResultCode Write(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: Error conditions.
|
|
||||||
long writePosition = context.RequestData.ReadInt64();
|
long writePosition = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
if (writePosition > _storage.Data.Length)
|
||||||
|
{
|
||||||
|
return ResultCode.OutOfBounds;
|
||||||
|
}
|
||||||
|
|
||||||
(long position, long size) = context.Request.GetBufferType0x21();
|
(long position, long size) = context.Request.GetBufferType0x21();
|
||||||
|
|
||||||
|
size = Math.Min(size, _storage.Data.Length - writePosition);
|
||||||
|
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
{
|
{
|
||||||
long maxSize = _storage.Data.Length - writePosition;
|
long maxSize = _storage.Data.Length - writePosition;
|
||||||
|
@ -50,23 +56,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
// Read(u64) -> buffer<bytes, 0x22>
|
// Read(u64) -> buffer<bytes, 0x22>
|
||||||
public ResultCode Read(ServiceCtx context)
|
public ResultCode Read(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: Error conditions.
|
|
||||||
long readPosition = context.RequestData.ReadInt64();
|
long readPosition = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
|
if (readPosition > _storage.Data.Length)
|
||||||
|
{
|
||||||
|
return ResultCode.OutOfBounds;
|
||||||
|
}
|
||||||
|
|
||||||
(long position, long size) = context.Request.GetBufferType0x22();
|
(long position, long size) = context.Request.GetBufferType0x22();
|
||||||
|
|
||||||
byte[] data;
|
size = Math.Min(size, _storage.Data.Length - readPosition);
|
||||||
|
|
||||||
if (_storage.Data.Length > size)
|
byte[] data = new byte[size];
|
||||||
{
|
|
||||||
data = new byte[size];
|
|
||||||
|
|
||||||
Buffer.BlockCopy(_storage.Data, 0, data, 0, (int)size);
|
Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data = _storage.Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Memory.WriteBytes(position, data);
|
context.Memory.WriteBytes(position, data);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
||||||
|
|
||||||
NoMessages = (3 << ErrorCodeShift) | ModuleId,
|
NoMessages = (3 << ErrorCodeShift) | ModuleId,
|
||||||
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
|
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
|
||||||
|
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
|
||||||
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
|
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue