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>
|
||||
{
|
||||
{ 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,14 +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);
|
||||
|
||||
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 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);
|
||||
_normalOutDataEvent = new KEvent(system);
|
||||
_interactiveOutDataEvent = new KEvent(system);
|
||||
|
||||
_applet = AppletManager.Create(appletId, system);
|
||||
_inData = new AppletFifo<byte[]>();
|
||||
_outData = new AppletFifo<byte[]>();
|
||||
|
||||
|
||||
_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!");
|
||||
|
@ -47,6 +62,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"GetAppletStateChangedEvent called.");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -54,13 +71,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// Start()
|
||||
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)]
|
||||
// GetResult()
|
||||
public ResultCode GetResult(ServiceCtx context)
|
||||
{
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"GetResult called.");
|
||||
|
||||
return (ResultCode)_applet.GetResult();
|
||||
}
|
||||
|
||||
|
@ -70,7 +92,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
_inData.Push(data.Data);
|
||||
_normalSession.Push(data.Data);
|
||||
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"PushInData called.");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -79,10 +103,74 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// PopOutData() -> object<nn::am::service::IStorage>
|
||||
public ResultCode PopOutData(ServiceCtx context)
|
||||
{
|
||||
byte[] data = _outData.Pop();
|
||||
byte[] data = _normalSession.Pop();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,30 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
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 +35,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 +68,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 +103,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);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
|||
|
||||
NoMessages = (3 << ErrorCodeShift) | ModuleId,
|
||||
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
|
||||
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
|
||||
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue