mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-07-24 07:27:10 +02:00
Merge Latest Ryujinx (Unstable)
This commit is contained in:
parent
aaefc0a9e5
commit
12ab8bc3e2
1237 changed files with 48656 additions and 21399 deletions
|
@ -4,6 +4,7 @@ using LibHac.Fs;
|
|||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Horizon.Sdk.Account;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -11,7 +12,7 @@ using System.Linq;
|
|||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
{
|
||||
public class AccountManager
|
||||
public class AccountManager : IEmulatorAccountManager
|
||||
{
|
||||
public static readonly UserId DefaultUserId = new("00000000000000010000000000000000");
|
||||
|
||||
|
@ -106,6 +107,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
_accountSaveDataManager.Save(_profiles);
|
||||
}
|
||||
|
||||
public void OpenUserOnlinePlay(Uid userId)
|
||||
{
|
||||
OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
|
||||
}
|
||||
|
||||
public void OpenUserOnlinePlay(UserId userId)
|
||||
{
|
||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||
|
@ -127,6 +133,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
_accountSaveDataManager.Save(_profiles);
|
||||
}
|
||||
|
||||
public void CloseUserOnlinePlay(Uid userId)
|
||||
{
|
||||
CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
|
||||
}
|
||||
|
||||
public void CloseUserOnlinePlay(UserId userId)
|
||||
{
|
||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext;
|
||||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -20,6 +22,9 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||
private readonly UserId _userId;
|
||||
#pragma warning restore IDE0052
|
||||
|
||||
private byte[] _cachedTokenData;
|
||||
private DateTime _cachedTokenExpiry;
|
||||
|
||||
public ManagerServer(UserId userId)
|
||||
{
|
||||
_userId = userId;
|
||||
|
@ -37,11 +42,6 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||
|
||||
credentials.Key.KeyId = parameters.ToString();
|
||||
|
||||
var header = new JwtHeader(credentials)
|
||||
{
|
||||
{ "jku", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" },
|
||||
};
|
||||
|
||||
byte[] rawUserId = new byte[0x10];
|
||||
RandomNumberGenerator.Fill(rawUserId);
|
||||
|
||||
|
@ -51,23 +51,25 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||
byte[] deviceAccountId = new byte[0x10];
|
||||
RandomNumberGenerator.Fill(deviceId);
|
||||
|
||||
var payload = new JwtPayload
|
||||
var descriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
{ "sub", Convert.ToHexString(rawUserId).ToLower() },
|
||||
{ "aud", "ed9e2f05d286f7b8" },
|
||||
{ "di", Convert.ToHexString(deviceId).ToLower() },
|
||||
{ "sn", "XAW10000000000" },
|
||||
{ "bs:did", Convert.ToHexString(deviceAccountId).ToLower() },
|
||||
{ "iss", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com" },
|
||||
{ "typ", "id_token" },
|
||||
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
|
||||
{ "jti", Guid.NewGuid().ToString() },
|
||||
{ "exp", (DateTimeOffset.UtcNow + TimeSpan.FromHours(3)).ToUnixTimeSeconds() },
|
||||
Subject = new GenericIdentity(Convert.ToHexString(rawUserId).ToLower()),
|
||||
SigningCredentials = credentials,
|
||||
Audience = "ed9e2f05d286f7b8",
|
||||
Issuer = "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com",
|
||||
TokenType = "id_token",
|
||||
IssuedAt = DateTime.UtcNow,
|
||||
Expires = DateTime.UtcNow + TimeSpan.FromHours(3),
|
||||
Claims = new Dictionary<string, object>
|
||||
{
|
||||
{ "jku", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" },
|
||||
{ "di", Convert.ToHexString(deviceId).ToLower() },
|
||||
{ "sn", "XAW10000000000" },
|
||||
{ "bs:did", Convert.ToHexString(deviceAccountId).ToLower() }
|
||||
}
|
||||
};
|
||||
|
||||
JwtSecurityToken securityToken = new(header, payload);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(securityToken);
|
||||
return new JsonWebTokenHandler().CreateToken(descriptor);
|
||||
}
|
||||
|
||||
public ResultCode CheckAvailability(ServiceCtx context)
|
||||
|
@ -145,7 +147,13 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||
}
|
||||
*/
|
||||
|
||||
byte[] tokenData = Encoding.ASCII.GetBytes(GenerateIdToken());
|
||||
if (_cachedTokenData == null || DateTime.UtcNow > _cachedTokenExpiry)
|
||||
{
|
||||
_cachedTokenExpiry = DateTime.UtcNow + TimeSpan.FromHours(3);
|
||||
_cachedTokenData = Encoding.ASCII.GetBytes(GenerateIdToken());
|
||||
}
|
||||
|
||||
byte[] tokenData = _cachedTokenData;
|
||||
|
||||
context.Memory.Write(bufferPosition, tokenData);
|
||||
context.ResponseData.Write(tokenData.Length);
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
|
||||
if (titleId == 0)
|
||||
{
|
||||
context.Device.UiHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId);
|
||||
context.Device.UIHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -524,7 +524,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value });
|
||||
|
||||
context.Device.UiHandler.ExecuteProgram(context.Device, kind, value);
|
||||
context.Device.UIHandler.ExecuteProgram(context.Device, kind, value);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
[Service("arp:r")]
|
||||
class IReader : IpcService
|
||||
{
|
||||
public IReader(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
{
|
||||
[Service("arp:w")]
|
||||
class IWriter : IpcService
|
||||
{
|
||||
public IWriter(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Input;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
|
||||
{
|
||||
class AudioIn : IAudioIn
|
||||
{
|
||||
private readonly AudioInputSystem _system;
|
||||
private readonly uint _processHandle;
|
||||
private readonly KernelContext _kernelContext;
|
||||
|
||||
public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle)
|
||||
{
|
||||
_system = system;
|
||||
_kernelContext = kernelContext;
|
||||
_processHandle = processHandle;
|
||||
}
|
||||
|
||||
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
|
||||
{
|
||||
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
|
||||
}
|
||||
|
||||
public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle)
|
||||
{
|
||||
return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle);
|
||||
}
|
||||
|
||||
public bool ContainsBuffer(ulong bufferTag)
|
||||
{
|
||||
return _system.ContainsBuffer(bufferTag);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_system.Dispose();
|
||||
|
||||
_kernelContext.Syscall.CloseHandle((int)_processHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool FlushBuffers()
|
||||
{
|
||||
return _system.FlushBuffers();
|
||||
}
|
||||
|
||||
public uint GetBufferCount()
|
||||
{
|
||||
return _system.GetBufferCount();
|
||||
}
|
||||
|
||||
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
|
||||
{
|
||||
return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount);
|
||||
}
|
||||
|
||||
public AudioDeviceState GetState()
|
||||
{
|
||||
return _system.GetState();
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
return _system.GetVolume();
|
||||
}
|
||||
|
||||
public KEvent RegisterBufferEvent()
|
||||
{
|
||||
IWritableEvent outEvent = _system.RegisterBufferEvent();
|
||||
|
||||
if (outEvent is AudioKernelEvent kernelEvent)
|
||||
{
|
||||
return kernelEvent.Event;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
_system.SetVolume(volume);
|
||||
}
|
||||
|
||||
public ResultCode Start()
|
||||
{
|
||||
return (ResultCode)_system.Start();
|
||||
}
|
||||
|
||||
public ResultCode Stop()
|
||||
{
|
||||
return (ResultCode)_system.Stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
|
||||
{
|
||||
class AudioInServer : DisposableIpcService
|
||||
{
|
||||
private readonly IAudioIn _impl;
|
||||
|
||||
public AudioInServer(IAudioIn impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetAudioInState() -> u32 state
|
||||
public ResultCode GetAudioInState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((uint)_impl.GetState());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// Start()
|
||||
public ResultCode Start(ServiceCtx context)
|
||||
{
|
||||
return _impl.Start();
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// Stop()
|
||||
public ResultCode StopAudioIn(ServiceCtx context)
|
||||
{
|
||||
return _impl.Stop();
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>)
|
||||
public ResultCode AppendAudioInBuffer(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.SendBuff[0].Position;
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendBuffer(bufferTag, ref data);
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// RegisterBufferEvent() -> handle<copy>
|
||||
public ResultCode RegisterBufferEvent(ServiceCtx context)
|
||||
{
|
||||
KEvent bufferEvent = _impl.RegisterBufferEvent();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags)
|
||||
public ResultCode GetReleasedAudioInBuffers(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
using WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size);
|
||||
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
|
||||
|
||||
context.ResponseData.Write(releasedCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// ContainsAudioInBuffer(u64 tag) -> b8
|
||||
public ResultCode ContainsAudioInBuffer(ServiceCtx context)
|
||||
{
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)] // 3.0.0+
|
||||
// AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>)
|
||||
public ResultCode AppendUacInBuffer(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.SendBuff[0].Position;
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
|
||||
}
|
||||
|
||||
[CommandCmif(8)] // 3.0.0+
|
||||
// AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>)
|
||||
public ResultCode AppendAudioInBufferAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, _) = context.Request.GetBufferType0x21();
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendBuffer(bufferTag, ref data);
|
||||
}
|
||||
|
||||
[CommandCmif(9)] // 3.0.0+
|
||||
// GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
|
||||
public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
|
||||
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
|
||||
|
||||
context.ResponseData.Write(releasedCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(10)] // 3.0.0+
|
||||
// AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>)
|
||||
public ResultCode AppendUacInBufferAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, _) = context.Request.GetBufferType0x21();
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
|
||||
}
|
||||
|
||||
[CommandCmif(11)] // 4.0.0+
|
||||
// GetAudioInBufferCount() -> u32
|
||||
public ResultCode GetAudioInBufferCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetBufferCount());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(12)] // 4.0.0+
|
||||
// SetAudioInVolume(s32)
|
||||
public ResultCode SetAudioInVolume(ServiceCtx context)
|
||||
{
|
||||
float volume = context.RequestData.ReadSingle();
|
||||
|
||||
_impl.SetVolume(volume);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(13)] // 4.0.0+
|
||||
// GetAudioInVolume() -> s32
|
||||
public ResultCode GetAudioInVolume(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetVolume());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(14)] // 6.0.0+
|
||||
// FlushAudioInBuffers() -> b8
|
||||
public ResultCode FlushAudioInBuffers(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.FlushBuffers());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
_impl.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
|
||||
{
|
||||
interface IAudioIn : IDisposable
|
||||
{
|
||||
AudioDeviceState GetState();
|
||||
|
||||
ResultCode Start();
|
||||
|
||||
ResultCode Stop();
|
||||
|
||||
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
|
||||
|
||||
// NOTE: This is broken by design... not quite sure what it's used for (if anything in production).
|
||||
ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle);
|
||||
|
||||
KEvent RegisterBufferEvent();
|
||||
|
||||
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
|
||||
|
||||
bool ContainsBuffer(ulong bufferTag);
|
||||
|
||||
uint GetBufferCount();
|
||||
|
||||
bool FlushBuffers();
|
||||
|
||||
void SetVolume(float volume);
|
||||
|
||||
float GetVolume();
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Input;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
|
||||
using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
class AudioInManager : IAudioInManager
|
||||
{
|
||||
private readonly AudioInManagerImpl _impl;
|
||||
|
||||
public AudioInManager(AudioInManagerImpl impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public string[] ListAudioIns(bool filtered)
|
||||
{
|
||||
return _impl.ListAudioIns(filtered);
|
||||
}
|
||||
|
||||
public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle)
|
||||
{
|
||||
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
|
||||
|
||||
ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audin:u")]
|
||||
class AudioInManagerServer : IpcService
|
||||
{
|
||||
private const int AudioInNameSize = 0x100;
|
||||
|
||||
private readonly IAudioInManager _impl;
|
||||
|
||||
public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { }
|
||||
|
||||
public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// ListAudioIns() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode ListAudioIns(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioIns(false);
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
|
||||
|
||||
position += AudioInNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
|
||||
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
|
||||
public ResultCode OpenAudioIn(ServiceCtx context)
|
||||
{
|
||||
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
|
||||
|
||||
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.WriteStruct(outputConfiguration);
|
||||
|
||||
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
|
||||
|
||||
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
|
||||
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
|
||||
|
||||
MakeObject(context, new AudioInServer(obj));
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(2)] // 3.0.0+
|
||||
// ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>)
|
||||
public ResultCode ListAudioInsAuto(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioIns(false);
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
|
||||
|
||||
position += AudioInNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)] // 3.0.0+
|
||||
// OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
|
||||
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 0x22> name)
|
||||
public ResultCode OpenAudioInAuto(ServiceCtx context)
|
||||
{
|
||||
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
|
||||
|
||||
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.WriteStruct(outputConfiguration);
|
||||
|
||||
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
|
||||
|
||||
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
|
||||
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
|
||||
|
||||
MakeObject(context, new AudioInServer(obj));
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(4)] // 3.0.0+
|
||||
// ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>)
|
||||
public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioIns(true);
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
|
||||
|
||||
position += AudioInNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)] // 5.0.0+
|
||||
// OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
|
||||
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
|
||||
public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context)
|
||||
{
|
||||
// NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1;
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
#pragma warning disable IDE0051, IDE0059 // Remove unused private member
|
||||
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
|
||||
#pragma warning restore IDE0051, IDE0059
|
||||
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
|
||||
|
||||
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.WriteStruct(outputConfiguration);
|
||||
|
||||
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
|
||||
|
||||
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
|
||||
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
|
||||
|
||||
MakeObject(context, new AudioInServer(obj));
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Output;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
|
||||
{
|
||||
class AudioOut : IAudioOut
|
||||
{
|
||||
private readonly AudioOutputSystem _system;
|
||||
private readonly uint _processHandle;
|
||||
private readonly KernelContext _kernelContext;
|
||||
|
||||
public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle)
|
||||
{
|
||||
_system = system;
|
||||
_kernelContext = kernelContext;
|
||||
_processHandle = processHandle;
|
||||
}
|
||||
|
||||
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
|
||||
{
|
||||
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
|
||||
}
|
||||
|
||||
public bool ContainsBuffer(ulong bufferTag)
|
||||
{
|
||||
return _system.ContainsBuffer(bufferTag);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_system.Dispose();
|
||||
|
||||
_kernelContext.Syscall.CloseHandle((int)_processHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool FlushBuffers()
|
||||
{
|
||||
return _system.FlushBuffers();
|
||||
}
|
||||
|
||||
public uint GetBufferCount()
|
||||
{
|
||||
return _system.GetBufferCount();
|
||||
}
|
||||
|
||||
public ulong GetPlayedSampleCount()
|
||||
{
|
||||
return _system.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
|
||||
{
|
||||
return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount);
|
||||
}
|
||||
|
||||
public AudioDeviceState GetState()
|
||||
{
|
||||
return _system.GetState();
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
return _system.GetVolume();
|
||||
}
|
||||
|
||||
public KEvent RegisterBufferEvent()
|
||||
{
|
||||
IWritableEvent outEvent = _system.RegisterBufferEvent();
|
||||
|
||||
if (outEvent is AudioKernelEvent kernelEvent)
|
||||
{
|
||||
return kernelEvent.Event;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
_system.SetVolume(volume);
|
||||
}
|
||||
|
||||
public ResultCode Start()
|
||||
{
|
||||
return (ResultCode)_system.Start();
|
||||
}
|
||||
|
||||
public ResultCode Stop()
|
||||
{
|
||||
return (ResultCode)_system.Stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
|
||||
{
|
||||
class AudioOutServer : DisposableIpcService
|
||||
{
|
||||
private readonly IAudioOut _impl;
|
||||
|
||||
public AudioOutServer(IAudioOut impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetAudioOutState() -> u32 state
|
||||
public ResultCode GetAudioOutState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((uint)_impl.GetState());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// Start()
|
||||
public ResultCode Start(ServiceCtx context)
|
||||
{
|
||||
return _impl.Start();
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// Stop()
|
||||
public ResultCode Stop(ServiceCtx context)
|
||||
{
|
||||
return _impl.Stop();
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer)
|
||||
public ResultCode AppendAudioOutBuffer(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.SendBuff[0].Position;
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendBuffer(bufferTag, ref data);
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// RegisterBufferEvent() -> handle<copy>
|
||||
public ResultCode RegisterBufferEvent(ServiceCtx context)
|
||||
{
|
||||
KEvent bufferEvent = _impl.RegisterBufferEvent();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags)
|
||||
public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
|
||||
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
|
||||
|
||||
context.ResponseData.Write(releasedCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// ContainsAudioOutBuffer(u64 tag) -> b8
|
||||
public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
|
||||
{
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)] // 3.0.0+
|
||||
// AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
|
||||
public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, _) = context.Request.GetBufferType0x21();
|
||||
|
||||
ulong bufferTag = context.RequestData.ReadUInt64();
|
||||
|
||||
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
|
||||
|
||||
return _impl.AppendBuffer(bufferTag, ref data);
|
||||
}
|
||||
|
||||
[CommandCmif(8)] // 3.0.0+
|
||||
// GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
|
||||
public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
|
||||
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
|
||||
|
||||
context.ResponseData.Write(releasedCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(9)] // 4.0.0+
|
||||
// GetAudioOutBufferCount() -> u32
|
||||
public ResultCode GetAudioOutBufferCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetBufferCount());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10)] // 4.0.0+
|
||||
// GetAudioOutPlayedSampleCount() -> u64
|
||||
public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetPlayedSampleCount());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)] // 4.0.0+
|
||||
// FlushAudioOutBuffers() -> b8
|
||||
public ResultCode FlushAudioOutBuffers(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.FlushBuffers());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(12)] // 6.0.0+
|
||||
// SetAudioOutVolume(s32)
|
||||
public ResultCode SetAudioOutVolume(ServiceCtx context)
|
||||
{
|
||||
float volume = context.RequestData.ReadSingle();
|
||||
|
||||
_impl.SetVolume(volume);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(13)] // 6.0.0+
|
||||
// GetAudioOutVolume() -> s32
|
||||
public ResultCode GetAudioOutVolume(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetVolume());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
_impl.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
|
||||
{
|
||||
interface IAudioOut : IDisposable
|
||||
{
|
||||
AudioDeviceState GetState();
|
||||
|
||||
ResultCode Start();
|
||||
|
||||
ResultCode Stop();
|
||||
|
||||
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
|
||||
|
||||
KEvent RegisterBufferEvent();
|
||||
|
||||
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
|
||||
|
||||
bool ContainsBuffer(ulong bufferTag);
|
||||
|
||||
uint GetBufferCount();
|
||||
|
||||
ulong GetPlayedSampleCount();
|
||||
|
||||
bool FlushBuffers();
|
||||
|
||||
void SetVolume(float volume);
|
||||
|
||||
float GetVolume();
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Output;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
|
||||
using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
class AudioOutManager : IAudioOutManager
|
||||
{
|
||||
private readonly AudioOutManagerImpl _impl;
|
||||
|
||||
public AudioOutManager(AudioOutManagerImpl impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public string[] ListAudioOuts()
|
||||
{
|
||||
return _impl.ListAudioOuts();
|
||||
}
|
||||
|
||||
public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume)
|
||||
{
|
||||
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
|
||||
|
||||
ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audout:u")]
|
||||
class AudioOutManagerServer : IpcService
|
||||
{
|
||||
private const int AudioOutNameSize = 0x100;
|
||||
|
||||
private readonly IAudioOutManager _impl;
|
||||
|
||||
public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { }
|
||||
|
||||
public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// ListAudioOuts() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode ListAudioOuts(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioOuts();
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
|
||||
|
||||
position += AudioOutNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 5> name_in)
|
||||
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
|
||||
public ResultCode OpenAudioOut(ServiceCtx context)
|
||||
{
|
||||
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
|
||||
|
||||
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.WriteStruct(outputConfiguration);
|
||||
|
||||
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
|
||||
|
||||
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
|
||||
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
|
||||
|
||||
MakeObject(context, new AudioOutServer(obj));
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(2)] // 3.0.0+
|
||||
// ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>)
|
||||
public ResultCode ListAudioOutsAuto(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioOuts();
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
|
||||
|
||||
position += AudioOutNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)] // 3.0.0+
|
||||
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 0x21> name_in)
|
||||
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
|
||||
public ResultCode OpenAudioOutAuto(ServiceCtx context)
|
||||
{
|
||||
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
|
||||
|
||||
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
|
||||
|
||||
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.WriteStruct(outputConfiguration);
|
||||
|
||||
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
|
||||
|
||||
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
|
||||
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
|
||||
|
||||
MakeObject(context, new AudioOutServer(obj));
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
using Ryujinx.Audio.Renderer.Device;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
class AudioDevice : IAudioDevice
|
||||
{
|
||||
private readonly VirtualDeviceSession[] _sessions;
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly ulong _appletResourceId;
|
||||
private readonly int _revision;
|
||||
#pragma warning restore IDE0052
|
||||
private readonly bool _isUsbDeviceSupported;
|
||||
|
||||
private readonly VirtualDeviceSessionRegistry _registry;
|
||||
private readonly KEvent _systemEvent;
|
||||
|
||||
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
|
||||
{
|
||||
_registry = registry;
|
||||
_appletResourceId = appletResourceId;
|
||||
_revision = revision;
|
||||
|
||||
BehaviourContext behaviourContext = new();
|
||||
behaviourContext.SetUserRevision(revision);
|
||||
|
||||
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
|
||||
_sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
|
||||
|
||||
// TODO: support the 3 different events correctly when we will have hot plugable audio devices.
|
||||
_systemEvent = new KEvent(context);
|
||||
_systemEvent.ReadableEvent.Signal();
|
||||
}
|
||||
|
||||
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
|
||||
{
|
||||
result = null;
|
||||
|
||||
foreach (VirtualDeviceSession session in _sessions)
|
||||
{
|
||||
if (session.Device.Name.Equals(name))
|
||||
{
|
||||
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = session;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetActiveAudioDeviceName()
|
||||
{
|
||||
VirtualDevice device = _registry.ActiveDevice;
|
||||
|
||||
if (!_isUsbDeviceSupported && device.IsUsbDevice())
|
||||
{
|
||||
device = _registry.DefaultDevice;
|
||||
}
|
||||
|
||||
return device.Name;
|
||||
}
|
||||
|
||||
public uint GetActiveChannelCount()
|
||||
{
|
||||
VirtualDevice device = _registry.ActiveDevice;
|
||||
|
||||
if (!_isUsbDeviceSupported && device.IsUsbDevice())
|
||||
{
|
||||
device = _registry.DefaultDevice;
|
||||
}
|
||||
|
||||
return device.ChannelCount;
|
||||
}
|
||||
|
||||
public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
|
||||
{
|
||||
if (TryGetDeviceByName(out VirtualDeviceSession result, name))
|
||||
{
|
||||
volume = result.Volume;
|
||||
}
|
||||
else
|
||||
{
|
||||
volume = 0.0f;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
|
||||
{
|
||||
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
|
||||
{
|
||||
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
|
||||
{
|
||||
result = _sessions[0];
|
||||
}
|
||||
|
||||
result.Volume = volume;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public string GetActiveAudioOutputDeviceName()
|
||||
{
|
||||
return _registry.ActiveDevice.GetOutputDeviceName();
|
||||
}
|
||||
|
||||
public string[] ListAudioDeviceName()
|
||||
{
|
||||
int deviceCount = _sessions.Length;
|
||||
|
||||
if (!_isUsbDeviceSupported)
|
||||
{
|
||||
deviceCount--;
|
||||
}
|
||||
|
||||
string[] result = new string[deviceCount];
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (VirtualDeviceSession session in _sessions)
|
||||
{
|
||||
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result[i] = session.Device.Name;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string[] ListAudioOutputDeviceName()
|
||||
{
|
||||
int deviceCount = _sessions.Length;
|
||||
|
||||
string[] result = new string[deviceCount];
|
||||
|
||||
for (int i = 0; i < deviceCount; i++)
|
||||
{
|
||||
result[i] = _sessions[i].Device.GetOutputDeviceName();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public KEvent QueryAudioDeviceInputEvent()
|
||||
{
|
||||
return _systemEvent;
|
||||
}
|
||||
|
||||
public KEvent QueryAudioDeviceOutputEvent()
|
||||
{
|
||||
return _systemEvent;
|
||||
}
|
||||
|
||||
public KEvent QueryAudioDeviceSystemEvent()
|
||||
{
|
||||
return _systemEvent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,320 +0,0 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
class AudioDeviceServer : IpcService
|
||||
{
|
||||
private const int AudioDeviceNameSize = 0x100;
|
||||
|
||||
private readonly IAudioDevice _impl;
|
||||
|
||||
public AudioDeviceServer(IAudioDevice impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode ListAudioDeviceName(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioDeviceName();
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
|
||||
|
||||
position += AudioDeviceNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
|
||||
public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
|
||||
{
|
||||
float volume = context.RequestData.ReadSingle();
|
||||
|
||||
ulong position = context.Request.SendBuff[0].Position;
|
||||
ulong size = context.Request.SendBuff[0].Size;
|
||||
|
||||
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
|
||||
|
||||
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
|
||||
public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
|
||||
{
|
||||
ulong position = context.Request.SendBuff[0].Position;
|
||||
ulong size = context.Request.SendBuff[0].Size;
|
||||
|
||||
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
|
||||
|
||||
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.Write(volume);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// GetActiveAudioDeviceName() -> buffer<bytes, 6>
|
||||
public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
|
||||
{
|
||||
string name = _impl.GetActiveAudioDeviceName();
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
|
||||
|
||||
if ((ulong)deviceNameBuffer.Length <= size)
|
||||
{
|
||||
context.Memory.Write(position, deviceNameBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// QueryAudioDeviceSystemEvent() -> handle<copy, event>
|
||||
public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
|
||||
{
|
||||
KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// GetActiveChannelCount() -> u32
|
||||
public ResultCode GetActiveChannelCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetActiveChannelCount());
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)] // 3.0.0+
|
||||
// ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
|
||||
public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioDeviceName();
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
|
||||
|
||||
position += AudioDeviceNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)] // 3.0.0+
|
||||
// SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
|
||||
public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
|
||||
{
|
||||
float volume = context.RequestData.ReadSingle();
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x21();
|
||||
|
||||
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
|
||||
|
||||
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
|
||||
}
|
||||
|
||||
[CommandCmif(8)] // 3.0.0+
|
||||
// GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
|
||||
public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x21();
|
||||
|
||||
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
|
||||
|
||||
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.ResponseData.Write(volume);
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10)] // 3.0.0+
|
||||
// GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
|
||||
public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
|
||||
{
|
||||
string name = _impl.GetActiveAudioDeviceName();
|
||||
|
||||
(ulong position, ulong size) = context.Request.GetBufferType0x22();
|
||||
|
||||
byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
|
||||
|
||||
if ((ulong)deviceNameBuffer.Length <= size)
|
||||
{
|
||||
context.Memory.Write(position, deviceNameBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)] // 3.0.0+
|
||||
// QueryAudioDeviceInputEvent() -> handle<copy, event>
|
||||
public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
|
||||
{
|
||||
KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(12)] // 3.0.0+
|
||||
// QueryAudioDeviceOutputEvent() -> handle<copy, event>
|
||||
public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
|
||||
{
|
||||
KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(13)] // 13.0.0+
|
||||
// GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
|
||||
public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
|
||||
{
|
||||
string name = _impl.GetActiveAudioOutputDeviceName();
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
|
||||
|
||||
if ((ulong)deviceNameBuffer.Length <= size)
|
||||
{
|
||||
context.Memory.Write(position, deviceNameBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(14)] // 13.0.0+
|
||||
// ListAudioOutputDeviceName() -> (u32, buffer<bytes, 6>)
|
||||
public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
|
||||
{
|
||||
string[] deviceNames = _impl.ListAudioOutputDeviceName();
|
||||
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
ulong size = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ulong basePosition = position;
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (string name in deviceNames)
|
||||
{
|
||||
byte[] buffer = Encoding.ASCII.GetBytes(name);
|
||||
|
||||
if ((position - basePosition) + (ulong)buffer.Length > size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
context.Memory.Write(position, buffer);
|
||||
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
|
||||
|
||||
position += AudioDeviceNameSize;
|
||||
count++;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
class AudioKernelEvent : IWritableEvent
|
||||
{
|
||||
public KEvent Event { get; }
|
||||
|
||||
public AudioKernelEvent(KEvent evnt)
|
||||
{
|
||||
Event = evnt;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Event.WritableEvent.Clear();
|
||||
}
|
||||
|
||||
public void Signal()
|
||||
{
|
||||
Event.WritableEvent.Signal();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
class AudioRenderer : IAudioRenderer
|
||||
{
|
||||
private readonly AudioRenderSystem _impl;
|
||||
|
||||
public AudioRenderer(AudioRenderSystem impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public ResultCode ExecuteAudioRendererRendering()
|
||||
{
|
||||
return (ResultCode)_impl.ExecuteAudioRendererRendering();
|
||||
}
|
||||
|
||||
public uint GetMixBufferCount()
|
||||
{
|
||||
return _impl.GetMixBufferCount();
|
||||
}
|
||||
|
||||
public uint GetRenderingTimeLimit()
|
||||
{
|
||||
return _impl.GetRenderingTimeLimit();
|
||||
}
|
||||
|
||||
public uint GetSampleCount()
|
||||
{
|
||||
return _impl.GetSampleCount();
|
||||
}
|
||||
|
||||
public uint GetSampleRate()
|
||||
{
|
||||
return _impl.GetSampleRate();
|
||||
}
|
||||
|
||||
public int GetState()
|
||||
{
|
||||
if (_impl.IsActive())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public ResultCode QuerySystemEvent(out KEvent systemEvent)
|
||||
{
|
||||
ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
if (outEvent is AudioKernelEvent kernelEvent)
|
||||
{
|
||||
systemEvent = kernelEvent.Event;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemEvent = null;
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
|
||||
{
|
||||
return (ResultCode)_impl.Update(output, performanceOutput, input);
|
||||
}
|
||||
|
||||
public void SetRenderingTimeLimit(uint percent)
|
||||
{
|
||||
_impl.SetRenderingTimeLimitPercent(percent);
|
||||
}
|
||||
|
||||
public ResultCode Start()
|
||||
{
|
||||
_impl.Start();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode Stop()
|
||||
{
|
||||
_impl.Stop();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_impl.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVoiceDropParameter(float voiceDropParameter)
|
||||
{
|
||||
_impl.SetVoiceDropParameter(voiceDropParameter);
|
||||
}
|
||||
|
||||
public float GetVoiceDropParameter()
|
||||
{
|
||||
return _impl.GetVoiceDropParameter();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
class AudioRendererServer : DisposableIpcService
|
||||
{
|
||||
private readonly IAudioRenderer _impl;
|
||||
|
||||
public AudioRendererServer(IAudioRenderer impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetSampleRate() -> u32
|
||||
public ResultCode GetSampleRate(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetSampleRate());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetSampleCount() -> u32
|
||||
public ResultCode GetSampleCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetSampleCount());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetMixBufferCount() -> u32
|
||||
public ResultCode GetMixBufferCount(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetMixBufferCount());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// GetState() -> u32
|
||||
public ResultCode GetState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(_impl.GetState());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
|
||||
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
|
||||
public ResultCode RequestUpdate(ServiceCtx context)
|
||||
{
|
||||
ulong inputPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inputSize = context.Request.SendBuff[0].Size;
|
||||
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
|
||||
ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size;
|
||||
|
||||
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
|
||||
|
||||
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.RentCleared(outputSize);
|
||||
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.RentCleared(performanceOutputSize);
|
||||
Memory<byte> output = outputOwner.Memory;
|
||||
Memory<byte> performanceOutput = performanceOutputOwner.Memory;
|
||||
|
||||
using MemoryHandle outputHandle = output.Pin();
|
||||
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
|
||||
|
||||
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.Memory.Write(outputPosition, output.Span);
|
||||
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// Start()
|
||||
public ResultCode Start(ServiceCtx context)
|
||||
{
|
||||
return _impl.Start();
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// Stop()
|
||||
public ResultCode Stop(ServiceCtx context)
|
||||
{
|
||||
return _impl.Stop();
|
||||
}
|
||||
|
||||
[CommandCmif(7)]
|
||||
// QuerySystemEvent() -> handle<copy, event>
|
||||
public ResultCode QuerySystemEvent(ServiceCtx context)
|
||||
{
|
||||
ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(8)]
|
||||
// SetAudioRendererRenderingTimeLimit(u32 limit)
|
||||
public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
|
||||
{
|
||||
uint limit = context.RequestData.ReadUInt32();
|
||||
|
||||
_impl.SetRenderingTimeLimit(limit);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(9)]
|
||||
// GetAudioRendererRenderingTimeLimit() -> u32 limit
|
||||
public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
|
||||
{
|
||||
uint limit = _impl.GetRenderingTimeLimit();
|
||||
|
||||
context.ResponseData.Write(limit);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10)] // 3.0.0+
|
||||
// RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
|
||||
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
|
||||
public ResultCode RequestUpdateAuto(ServiceCtx context)
|
||||
{
|
||||
(ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21();
|
||||
(ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0);
|
||||
(ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1);
|
||||
|
||||
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
|
||||
|
||||
Memory<byte> output = new byte[outputSize];
|
||||
Memory<byte> performanceOutput = new byte[performanceOutputSize];
|
||||
|
||||
using MemoryHandle outputHandle = output.Pin();
|
||||
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
|
||||
|
||||
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.Memory.Write(outputPosition, output.Span);
|
||||
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(11)] // 3.0.0+
|
||||
// ExecuteAudioRendererRendering()
|
||||
public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
|
||||
{
|
||||
return _impl.ExecuteAudioRendererRendering();
|
||||
}
|
||||
|
||||
[CommandCmif(12)] // 15.0.0+
|
||||
// SetVoiceDropParameter(f32 voiceDropParameter)
|
||||
public ResultCode SetVoiceDropParameter(ServiceCtx context)
|
||||
{
|
||||
float voiceDropParameter = context.RequestData.ReadSingle();
|
||||
|
||||
_impl.SetVoiceDropParameter(voiceDropParameter);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(13)] // 15.0.0+
|
||||
// GetVoiceDropParameter() -> f32 voiceDropParameter
|
||||
public ResultCode GetVoiceDropParameter(ServiceCtx context)
|
||||
{
|
||||
float voiceDropParameter = _impl.GetVoiceDropParameter();
|
||||
|
||||
context.ResponseData.Write(voiceDropParameter);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
_impl.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
interface IAudioDevice
|
||||
{
|
||||
string[] ListAudioDeviceName();
|
||||
ResultCode SetAudioDeviceOutputVolume(string name, float volume);
|
||||
ResultCode GetAudioDeviceOutputVolume(string name, out float volume);
|
||||
string GetActiveAudioDeviceName();
|
||||
KEvent QueryAudioDeviceSystemEvent();
|
||||
uint GetActiveChannelCount();
|
||||
KEvent QueryAudioDeviceInputEvent();
|
||||
KEvent QueryAudioDeviceOutputEvent();
|
||||
string GetActiveAudioOutputDeviceName();
|
||||
string[] ListAudioOutputDeviceName();
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
|
||||
{
|
||||
interface IAudioRenderer : IDisposable
|
||||
{
|
||||
uint GetSampleRate();
|
||||
uint GetSampleCount();
|
||||
uint GetMixBufferCount();
|
||||
int GetState();
|
||||
ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input);
|
||||
ResultCode Start();
|
||||
ResultCode Stop();
|
||||
ResultCode QuerySystemEvent(out KEvent systemEvent);
|
||||
void SetRenderingTimeLimit(uint percent);
|
||||
uint GetRenderingTimeLimit();
|
||||
ResultCode ExecuteAudioRendererRendering();
|
||||
void SetVoiceDropParameter(float voiceDropParameter);
|
||||
float GetVoiceDropParameter();
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
using Ryujinx.Audio.Renderer.Device;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
|
||||
using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
class AudioRendererManager : IAudioRendererManager
|
||||
{
|
||||
private readonly AudioRendererManagerImpl _impl;
|
||||
private readonly VirtualDeviceSessionRegistry _registry;
|
||||
|
||||
public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry)
|
||||
{
|
||||
_impl = impl;
|
||||
_registry = registry;
|
||||
}
|
||||
|
||||
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId)
|
||||
{
|
||||
outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
|
||||
{
|
||||
return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
|
||||
}
|
||||
|
||||
public ResultCode OpenAudioRenderer(
|
||||
ServiceCtx context,
|
||||
out IAudioRenderer obj,
|
||||
ref AudioRendererConfiguration parameter,
|
||||
ulong workBufferSize,
|
||||
ulong appletResourceUserId,
|
||||
KTransferMemory workBufferTransferMemory,
|
||||
uint processHandle)
|
||||
{
|
||||
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
|
||||
|
||||
ResultCode result = (ResultCode)_impl.OpenAudioRenderer(
|
||||
out AudioRenderSystem renderer,
|
||||
memoryManager,
|
||||
ref parameter,
|
||||
appletResourceUserId,
|
||||
workBufferTransferMemory.Address,
|
||||
workBufferTransferMemory.Size,
|
||||
processHandle,
|
||||
context.Device.Configuration.AudioVolume);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
obj = new AudioRenderer.AudioRenderer(renderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Server;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audren:u")]
|
||||
class AudioRendererManagerServer : IpcService
|
||||
{
|
||||
private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
|
||||
|
||||
private readonly IAudioRendererManager _impl;
|
||||
|
||||
public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { }
|
||||
|
||||
public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle)
|
||||
// -> object<nn::audio::detail::IAudioRenderer>
|
||||
public ResultCode OpenAudioRenderer(ServiceCtx context)
|
||||
{
|
||||
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
|
||||
ulong workBufferSize = context.RequestData.ReadUInt64();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0];
|
||||
KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle);
|
||||
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
|
||||
|
||||
ResultCode result = _impl.OpenAudioRenderer(
|
||||
context,
|
||||
out IAudioRenderer renderer,
|
||||
ref parameter,
|
||||
workBufferSize,
|
||||
appletResourceUserId,
|
||||
workBufferTransferMemory,
|
||||
processHandle);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
MakeObject(context, new AudioRendererServer(renderer));
|
||||
}
|
||||
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle);
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize
|
||||
public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
|
||||
{
|
||||
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
|
||||
|
||||
if (BehaviourContext.CheckValidRevision(parameter.Revision))
|
||||
{
|
||||
ulong size = _impl.GetWorkBufferSize(ref parameter);
|
||||
|
||||
context.ResponseData.Write(size);
|
||||
|
||||
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ResponseData.Write(0L);
|
||||
|
||||
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!");
|
||||
|
||||
return ResultCode.UnsupportedRevision;
|
||||
}
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
|
||||
public ResultCode GetAudioDeviceService(ServiceCtx context)
|
||||
{
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
MakeObject(context, new AudioDeviceServer(device));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(4)] // 4.0.0+
|
||||
// GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice>
|
||||
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
|
||||
{
|
||||
int revision = context.RequestData.ReadInt32();
|
||||
ulong appletResourceUserId = context.RequestData.ReadUInt64();
|
||||
|
||||
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
MakeObject(context, new AudioDeviceServer(device));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using Concentus.Structs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class Decoder : IDecoder
|
||||
{
|
||||
private readonly OpusDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount => _decoder.NumChannels;
|
||||
|
||||
public Decoder(int sampleRate, int channelsCount)
|
||||
{
|
||||
_decoder = new OpusDecoder(sampleRate, channelsCount);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
using Concentus;
|
||||
using Concentus.Enums;
|
||||
using Concentus.Structs;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
static class DecoderCommon
|
||||
{
|
||||
private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
|
||||
{
|
||||
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
|
||||
|
||||
numSamples = result;
|
||||
|
||||
if (result == OpusError.OPUS_INVALID_PACKET)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
else if (result == OpusError.OPUS_BAD_ARG)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public static ResultCode DecodeInterleaved(
|
||||
this IDecoder decoder,
|
||||
bool reset,
|
||||
ReadOnlySpan<byte> input,
|
||||
out short[] outPcmData,
|
||||
ulong outputSize,
|
||||
out uint outConsumed,
|
||||
out int outSamples)
|
||||
{
|
||||
outPcmData = null;
|
||||
outConsumed = 0;
|
||||
outSamples = 0;
|
||||
|
||||
int streamSize = input.Length;
|
||||
|
||||
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
|
||||
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
|
||||
uint totalSize = header.length + (uint)headerSize;
|
||||
|
||||
if (totalSize > streamSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
|
||||
|
||||
ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
|
||||
{
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
|
||||
outPcmData = new short[numSamples * decoder.ChannelsCount];
|
||||
|
||||
if (reset)
|
||||
{
|
||||
decoder.ResetState();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
|
||||
outConsumed = totalSize;
|
||||
}
|
||||
catch (OpusException)
|
||||
{
|
||||
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
|
||||
return ResultCode.OpusInvalidInput;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
interface IDecoder
|
||||
{
|
||||
int SampleRate { get; }
|
||||
int ChannelsCount { get; }
|
||||
|
||||
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
|
||||
void ResetState();
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class IHardwareOpusDecoder : IpcService
|
||||
{
|
||||
private readonly IDecoder _decoder;
|
||||
private readonly OpusDecoderFlags _flags;
|
||||
|
||||
public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
|
||||
{
|
||||
_decoder = new Decoder(sampleRate, channelsCount);
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
|
||||
{
|
||||
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
|
||||
public ResultCode DecodeInterleavedOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
|
||||
}
|
||||
|
||||
[CommandCmif(4)] // 6.0.0+
|
||||
// DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandCmif(5)] // 6.0.0+
|
||||
// DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
|
||||
{
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandCmif(6)] // 6.0.0+
|
||||
// DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandCmif(7)] // 6.0.0+
|
||||
// DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandCmif(8)] // 7.0.0+
|
||||
// DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleaved(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
|
||||
}
|
||||
|
||||
[CommandCmif(9)] // 7.0.0+
|
||||
// DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
|
||||
public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
|
||||
{
|
||||
bool reset = context.RequestData.ReadBoolean();
|
||||
|
||||
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
|
||||
}
|
||||
|
||||
private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
|
||||
{
|
||||
ulong inPosition = context.Request.SendBuff[0].Position;
|
||||
ulong inSize = context.Request.SendBuff[0].Size;
|
||||
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
ulong outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
|
||||
|
||||
ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
|
||||
|
||||
context.ResponseData.Write(outConsumed);
|
||||
context.ResponseData.Write(outSamples);
|
||||
|
||||
if (withPerf)
|
||||
{
|
||||
// This is the time the DSP took to process the request, TODO: fill this.
|
||||
context.ResponseData.Write(0UL);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
using Concentus.Structs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
|
||||
{
|
||||
class MultiSampleDecoder : IDecoder
|
||||
{
|
||||
private readonly OpusMSDecoder _decoder;
|
||||
|
||||
public int SampleRate => _decoder.SampleRate;
|
||||
public int ChannelsCount { get; }
|
||||
|
||||
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
|
||||
{
|
||||
ChannelsCount = channelsCount;
|
||||
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||
}
|
||||
|
||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
||||
{
|
||||
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_decoder.ResetState();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audctl")]
|
||||
class IAudioController : IpcService
|
||||
{
|
||||
public IAudioController(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
interface IAudioInManager
|
||||
{
|
||||
public string[] ListAudioIns(bool filtered);
|
||||
|
||||
public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audin:a")]
|
||||
class IAudioInManagerForApplet : IpcService
|
||||
{
|
||||
public IAudioInManagerForApplet(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audin:d")]
|
||||
class IAudioInManagerForDebugger : IpcService
|
||||
{
|
||||
public IAudioInManagerForDebugger(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
interface IAudioOutManager
|
||||
{
|
||||
public string[] ListAudioOuts();
|
||||
|
||||
public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audout:a")]
|
||||
class IAudioOutManagerForApplet : IpcService
|
||||
{
|
||||
public IAudioOutManagerForApplet(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audout:d")]
|
||||
class IAudioOutManagerForDebugger : IpcService
|
||||
{
|
||||
public IAudioOutManagerForDebugger(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
interface IAudioRendererManager
|
||||
{
|
||||
// TODO: Remove ServiceCtx argument
|
||||
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
|
||||
ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId);
|
||||
|
||||
// TODO: Remove ServiceCtx argument
|
||||
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
|
||||
ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle);
|
||||
|
||||
ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audren:a")]
|
||||
class IAudioRendererManagerForApplet : IpcService
|
||||
{
|
||||
public IAudioRendererManagerForApplet(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audren:d")]
|
||||
class IAudioRendererManagerForDebugger : IpcService
|
||||
{
|
||||
public IAudioRendererManagerForDebugger(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("auddev")] // 6.0.0+
|
||||
class IAudioSnoopManager : IpcService
|
||||
{
|
||||
public IAudioSnoopManager(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audrec:u")]
|
||||
class IFinalOutputRecorderManager : IpcService
|
||||
{
|
||||
public IFinalOutputRecorderManager(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audrec:a")]
|
||||
class IFinalOutputRecorderManagerForApplet : IpcService
|
||||
{
|
||||
public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("audrec:d")]
|
||||
class IFinalOutputRecorderManagerForDebugger : IpcService
|
||||
{
|
||||
public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.Types;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
[Service("hwopus")]
|
||||
class IHardwareOpusDecoderManager : IpcService
|
||||
{
|
||||
public IHardwareOpusDecoderManager(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(0)]
|
||||
// Initialize(bytes<8, 4>, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode Initialize(ServiceCtx context)
|
||||
{
|
||||
int sampleRate = context.RequestData.ReadInt32();
|
||||
int channelsCount = context.RequestData.ReadInt32();
|
||||
|
||||
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetWorkBufferSize(bytes<8, 4>) -> u32
|
||||
public ResultCode GetWorkBufferSize(ServiceCtx context)
|
||||
{
|
||||
int sampleRate = context.RequestData.ReadInt32();
|
||||
int channelsCount = context.RequestData.ReadInt32();
|
||||
|
||||
int opusDecoderSize = GetOpusDecoderSize(channelsCount);
|
||||
|
||||
int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
|
||||
int totalSize = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)] // 3.0.0+
|
||||
// InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode InitializeForMultiStream(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
|
||||
|
||||
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)] // 3.0.0+
|
||||
// GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
|
||||
public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
|
||||
|
||||
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
|
||||
|
||||
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)] // 12.0.0+
|
||||
// InitializeEx(OpusParametersEx, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode InitializeEx(ServiceCtx context)
|
||||
{
|
||||
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
|
||||
|
||||
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
|
||||
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)] // 12.0.0+
|
||||
// GetWorkBufferSizeEx(OpusParametersEx) -> u32
|
||||
public ResultCode GetWorkBufferSizeEx(ServiceCtx context)
|
||||
{
|
||||
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
|
||||
|
||||
int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
|
||||
|
||||
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + 1536 + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)] // 12.0.0+
|
||||
// InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
|
||||
public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
|
||||
|
||||
byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.AsSpan()).ToArray();
|
||||
|
||||
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
|
||||
MakeObject(context, new IHardwareOpusDecoder(
|
||||
parameters.SampleRate,
|
||||
parameters.ChannelsCount,
|
||||
parameters.NumberOfStreams,
|
||||
parameters.NumberOfStereoStreams,
|
||||
parameters.Flags,
|
||||
mappings));
|
||||
|
||||
// Close transfer memory immediately as we don't use it.
|
||||
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(7)] // 12.0.0+
|
||||
// GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
|
||||
public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
|
||||
{
|
||||
ulong parametersAddress = context.Request.PtrBuff[0].Position;
|
||||
|
||||
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
|
||||
|
||||
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
|
||||
|
||||
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
|
||||
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
|
||||
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
|
||||
int totalSize = opusDecoderSize + streamSize + frameSize;
|
||||
|
||||
context.ResponseData.Write(totalSize);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(8)] // 16.0.0+
|
||||
// GetWorkBufferSizeExEx(OpusParametersEx) -> u32
|
||||
public ResultCode GetWorkBufferSizeExEx(ServiceCtx context)
|
||||
{
|
||||
// NOTE: GetWorkBufferSizeEx use hardcoded values to compute the returned size.
|
||||
// GetWorkBufferSizeExEx fixes that by using dynamic values.
|
||||
// Since we're already doing that, it's fine to call it directly.
|
||||
|
||||
return GetWorkBufferSizeEx(context);
|
||||
}
|
||||
|
||||
[CommandCmif(9)] // 16.0.0+
|
||||
// GetWorkBufferSizeForMultiStreamExEx(buffer<unknown<0x118>, 0x19>) -> u32
|
||||
public ResultCode GetWorkBufferSizeForMultiStreamExEx(ServiceCtx context)
|
||||
{
|
||||
// NOTE: GetWorkBufferSizeForMultiStreamEx use hardcoded values to compute the returned size.
|
||||
// GetWorkBufferSizeForMultiStreamExEx fixes that by using dynamic values.
|
||||
// Since we're already doing that, it's fine to call it directly.
|
||||
|
||||
return GetWorkBufferSizeForMultiStreamEx(context);
|
||||
}
|
||||
|
||||
private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
|
||||
{
|
||||
if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coupledSize = GetOpusDecoderSize(2);
|
||||
int monoSize = GetOpusDecoderSize(1);
|
||||
|
||||
return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
|
||||
Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
|
||||
}
|
||||
|
||||
private static int Align4(int value)
|
||||
{
|
||||
return BitUtils.AlignUp(value, 4);
|
||||
}
|
||||
|
||||
private static int GetOpusDecoderSize(int channelsCount)
|
||||
{
|
||||
const int SilkDecoderSize = 0x2160;
|
||||
|
||||
if (channelsCount < 1 || channelsCount > 2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int celtDecoderSize = GetCeltDecoderSize(channelsCount);
|
||||
int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
|
||||
|
||||
return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
|
||||
}
|
||||
|
||||
private static int GetOpusDecoderAllocSize(int channelsCount)
|
||||
{
|
||||
return (channelsCount * 0x800 + 0x4803) & -0x800;
|
||||
}
|
||||
|
||||
private static int GetCeltDecoderSize(int channelsCount)
|
||||
{
|
||||
const int DecodeBufferSize = 0x2030;
|
||||
const int Overlap = 120;
|
||||
const int EBandsCount = 21;
|
||||
|
||||
return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Audio
|
||||
{
|
||||
enum ResultCode
|
||||
{
|
||||
ModuleId = 153,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
DeviceNotFound = (1 << ErrorCodeShift) | ModuleId,
|
||||
UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId,
|
||||
UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId,
|
||||
BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId,
|
||||
OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId,
|
||||
TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId,
|
||||
InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId,
|
||||
InvalidOperation = (513 << ErrorCodeShift) | ModuleId,
|
||||
InvalidHandle = (1536 << ErrorCodeShift) | ModuleId,
|
||||
OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[Flags]
|
||||
enum OpusDecoderFlags : uint
|
||||
{
|
||||
None,
|
||||
LargeFrameSize = 1 << 0,
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x110)]
|
||||
struct OpusMultiStreamParameters
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public Array64<uint> ChannelMappings;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x118)]
|
||||
struct OpusMultiStreamParametersEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public int NumberOfStreams;
|
||||
public int NumberOfStereoStreams;
|
||||
public OpusDecoderFlags Flags;
|
||||
|
||||
Array4<byte> Padding1;
|
||||
|
||||
public Array64<uint> ChannelMappings;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct OpusPacketHeader
|
||||
{
|
||||
public uint length;
|
||||
public uint finalRange;
|
||||
|
||||
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
|
||||
{
|
||||
OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
|
||||
|
||||
header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
|
||||
header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Audio.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct OpusParametersEx
|
||||
{
|
||||
public int SampleRate;
|
||||
public int ChannelsCount;
|
||||
public OpusDecoderFlags Flags;
|
||||
|
||||
Array4<byte> Padding1;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Caps.Types;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Caps
|
||||
|
@ -118,7 +118,11 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||
}
|
||||
|
||||
// NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data.
|
||||
Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath);
|
||||
using var bitmap = new SKBitmap(new SKImageInfo(1280, 720, SKColorType.Rgba8888));
|
||||
Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length);
|
||||
using var data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
|
||||
using var file = File.OpenWrite(filePath);
|
||||
data.SaveTo(file);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
|
|||
errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
|
||||
errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
|
||||
|
||||
if (cpuContext != null)
|
||||
if (!cpuContext.IsEmpty)
|
||||
{
|
||||
errorReport.AppendLine("CPU Context:");
|
||||
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend
|
||||
{
|
||||
[Service("friend:a", FriendServicePermissionLevel.Administrator)]
|
||||
[Service("friend:m", FriendServicePermissionLevel.Manager)]
|
||||
[Service("friend:s", FriendServicePermissionLevel.System)]
|
||||
[Service("friend:u", FriendServicePermissionLevel.User)]
|
||||
[Service("friend:v", FriendServicePermissionLevel.Viewer)]
|
||||
class IServiceCreator : IpcService
|
||||
{
|
||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
||||
|
||||
public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_permissionLevel = permissionLevel;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService>
|
||||
public ResultCode CreateFriendService(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IFriendService(_permissionLevel));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)] // 2.0.0+
|
||||
// CreateNotificationService(nn::account::Uid userId) -> object<nn::friends::detail::ipc::INotificationService>
|
||||
public ResultCode CreateNotificationService(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
MakeObject(context, new INotificationService(context, userId, _permissionLevel));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)] // 4.0.0+
|
||||
// CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService>
|
||||
public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Friend
|
||||
{
|
||||
enum ResultCode
|
||||
{
|
||||
ModuleId = 121,
|
||||
ErrorCodeShift = 9,
|
||||
|
||||
Success = 0,
|
||||
|
||||
InvalidArgument = (2 << ErrorCodeShift) | ModuleId,
|
||||
InternetRequestDenied = (6 << ErrorCodeShift) | ModuleId,
|
||||
NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId,
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)]
|
||||
struct Friend
|
||||
{
|
||||
public UserId UserId;
|
||||
public long NetworkUserId;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)]
|
||||
public string Nickname;
|
||||
|
||||
public UserPresence presence;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsFavourite;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsNew;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)]
|
||||
readonly char[] Unknown;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsValid;
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct FriendFilter
|
||||
{
|
||||
public PresenceStatusFilter PresenceStatus;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsFavoriteOnly;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsSameAppPresenceOnly;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsSameAppPlayedOnly;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsArbitraryAppPlayedOnly;
|
||||
|
||||
public long PresenceGroupId;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
{
|
||||
enum PresenceStatus : uint
|
||||
{
|
||||
Offline,
|
||||
Online,
|
||||
OnlinePlay,
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
{
|
||||
enum PresenceStatusFilter : uint
|
||||
{
|
||||
None,
|
||||
Online,
|
||||
OnlinePlay,
|
||||
OnlineOrOnlinePlay,
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x8)]
|
||||
struct UserPresence
|
||||
{
|
||||
public UserId UserId;
|
||||
public long LastTimeOnlineTimestamp;
|
||||
public PresenceStatus Status;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool SamePresenceGroupApplication;
|
||||
|
||||
public Array3<byte> Unknown;
|
||||
private AppKeyValueStorageHolder _appKeyValueStorage;
|
||||
|
||||
public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
|
||||
private struct AppKeyValueStorageHolder
|
||||
{
|
||||
public const int Size = 0xC0;
|
||||
}
|
||||
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||
{
|
||||
class IDaemonSuspendSessionService : IpcService
|
||||
{
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
||||
#pragma warning restore IDE0052
|
||||
|
||||
public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_permissionLevel = permissionLevel;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,374 +0,0 @@
|
|||
using LibHac.Ns;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||
{
|
||||
class IFriendService : IpcService
|
||||
{
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
||||
#pragma warning restore IDE0052
|
||||
private KEvent _completionEvent;
|
||||
|
||||
public IFriendService(FriendServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_permissionLevel = permissionLevel;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetCompletionEvent() -> handle<copy>
|
||||
public ResultCode GetCompletionEvent(ServiceCtx context)
|
||||
{
|
||||
_completionEvent ??= new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_completionEvent.ReadableEvent, out int completionEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
_completionEvent.WritableEvent.Signal();
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// nn::friends::Cancel()
|
||||
public ResultCode Cancel(ServiceCtx context)
|
||||
{
|
||||
// TODO: Original service sets an internal field to 1 here. Determine usage.
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10100)]
|
||||
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
|
||||
public ResultCode GetFriendListIds(ServiceCtx context)
|
||||
{
|
||||
int offset = context.RequestData.ReadInt32();
|
||||
|
||||
// Padding
|
||||
context.RequestData.ReadInt32();
|
||||
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
||||
context.ResponseData.Write(0);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
|
||||
{
|
||||
UserId = userId.ToString(),
|
||||
offset,
|
||||
filter.PresenceStatus,
|
||||
filter.IsFavoriteOnly,
|
||||
filter.IsSameAppPresenceOnly,
|
||||
filter.IsSameAppPlayedOnly,
|
||||
filter.IsArbitraryAppPlayedOnly,
|
||||
filter.PresenceGroupId,
|
||||
});
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10101)]
|
||||
// nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// -> int outCount, array<nn::friends::detail::FriendImpl, 0x6>
|
||||
public ResultCode GetFriendList(ServiceCtx context)
|
||||
{
|
||||
int offset = context.RequestData.ReadInt32();
|
||||
|
||||
// Padding
|
||||
context.RequestData.ReadInt32();
|
||||
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
||||
context.ResponseData.Write(0);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
|
||||
{
|
||||
UserId = userId.ToString(),
|
||||
offset,
|
||||
filter.PresenceStatus,
|
||||
filter.IsFavoriteOnly,
|
||||
filter.IsSameAppPresenceOnly,
|
||||
filter.IsSameAppPlayedOnly,
|
||||
filter.IsArbitraryAppPlayedOnly,
|
||||
filter.PresenceGroupId,
|
||||
});
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10120)] // 10.0.0+
|
||||
// nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool
|
||||
public ResultCode IsFriendListCacheAvailable(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise.
|
||||
// NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check.
|
||||
context.ResponseData.Write(true);
|
||||
|
||||
// TODO: Since we don't support friend features, it's fine to stub it for now.
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10121)] // 10.0.0+
|
||||
// nn::friends::EnsureFriendListAvailable(nn::account::Uid userId)
|
||||
public ResultCode EnsureFriendListAvailable(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id.
|
||||
// Since we don't support friend features, it's fine to stub it for now.
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10400)]
|
||||
// nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>)
|
||||
public ResultCode GetBlockedUserListIds(ServiceCtx context)
|
||||
{
|
||||
int offset = context.RequestData.ReadInt32();
|
||||
|
||||
// Padding
|
||||
context.RequestData.ReadInt32();
|
||||
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
// There are no friends blocked, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
||||
context.ResponseData.Write(0);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { offset, UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10420)]
|
||||
// nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool
|
||||
public ResultCode CheckBlockedUserListAvailability(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
// Yes, it is available.
|
||||
context.ResponseData.Write(true);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10600)]
|
||||
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
|
||||
public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
context.Device.System.AccountManager.OpenUserOnlinePlay(userId);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10601)]
|
||||
// nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId)
|
||||
public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
context.Device.System.AccountManager.CloseUserOnlinePlay(userId);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10610)]
|
||||
// nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
|
||||
public ResultCode UpdateUserPresence(ServiceCtx context)
|
||||
{
|
||||
UserId uuid = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
||||
ulong position = context.Request.PtrBuff[0].Position;
|
||||
ulong size = context.Request.PtrBuff[0].Size;
|
||||
|
||||
ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
|
||||
|
||||
if (uuid.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10700)]
|
||||
// nn::friends::GetPlayHistoryRegistrationKey(b8 unknown, nn::account::Uid) -> buffer<nn::friends::PlayHistoryRegistrationKey, 0x1a>
|
||||
public ResultCode GetPlayHistoryRegistrationKey(ServiceCtx context)
|
||||
{
|
||||
bool unknownBool = context.RequestData.ReadBoolean();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x40UL);
|
||||
|
||||
ulong bufferPosition = context.Request.RecvListBuff[0].Position;
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance.
|
||||
|
||||
byte[] randomBytes = new byte[8];
|
||||
|
||||
Random.Shared.NextBytes(randomBytes);
|
||||
|
||||
// NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance.
|
||||
// Then call nn::friends::detail::service::core::AccountStorageManager::GetInstance and store the instance.
|
||||
// Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid.
|
||||
// And store it in the savedata 8000000000000080 in the friends:/uid.bin file.
|
||||
|
||||
Array16<byte> randomGuid = new();
|
||||
|
||||
Guid.NewGuid().ToByteArray().AsSpan().CopyTo(randomGuid.AsSpan());
|
||||
|
||||
PlayHistoryRegistrationKey playHistoryRegistrationKey = new()
|
||||
{
|
||||
Type = 0x101,
|
||||
KeyIndex = (byte)(randomBytes[0] & 7),
|
||||
UserIdBool = 0, // TODO: Find it.
|
||||
UnknownBool = (byte)(unknownBool ? 1 : 0), // TODO: Find it.
|
||||
Reserved = new Array11<byte>(),
|
||||
Uuid = randomGuid,
|
||||
};
|
||||
|
||||
ReadOnlySpan<byte> playHistoryRegistrationKeyBuffer = SpanHelpers.AsByteSpan(ref playHistoryRegistrationKey);
|
||||
|
||||
/*
|
||||
|
||||
NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer).
|
||||
We currently don't support play history and online services so we can use a blank key for now.
|
||||
Code for reference:
|
||||
|
||||
byte[] hmacKey = new byte[0x20];
|
||||
|
||||
HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey);
|
||||
byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer);
|
||||
|
||||
*/
|
||||
|
||||
context.Memory.Write(bufferPosition, playHistoryRegistrationKeyBuffer);
|
||||
context.Memory.Write(bufferPosition + 0x20, new byte[0x20]); // HmacHash
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10702)]
|
||||
// nn::friends::AddPlayHistory(nn::account::Uid, u64, pid, buffer<nn::friends::PlayHistoryRegistrationKey, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>)
|
||||
public ResultCode AddPlayHistory(ServiceCtx context)
|
||||
{
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ulong pid = context.Request.HandleDesc.PId;
|
||||
|
||||
ulong playHistoryRegistrationKeyPosition = context.Request.PtrBuff[0].Position;
|
||||
ulong playHistoryRegistrationKeySize = context.Request.PtrBuff[0].Size;
|
||||
|
||||
ulong inAppScreenName1Position = context.Request.PtrBuff[1].Position;
|
||||
#pragma warning restore IDE0059
|
||||
ulong inAppScreenName1Size = context.Request.PtrBuff[1].Size;
|
||||
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ulong inAppScreenName2Position = context.Request.PtrBuff[2].Position;
|
||||
#pragma warning restore IDE0059
|
||||
ulong inAppScreenName2Size = context.Request.PtrBuff[2].Size;
|
||||
|
||||
if (userId.IsNull || inAppScreenName1Size > 0x48 || inAppScreenName2Size > 0x48)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
/*
|
||||
|
||||
NOTE: The service calls nn::friends::detail::service::core::PlayHistoryManager to store informations using the registration key computed in GetPlayHistoryRegistrationKey.
|
||||
Then calls nn::friends::detail::service::core::FriendListManager to update informations on the friend list.
|
||||
We currently don't support play history and online services so it's fine to do nothing.
|
||||
|
||||
*/
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||
{
|
||||
class INotificationService : DisposableIpcService
|
||||
{
|
||||
private readonly UserId _userId;
|
||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
private readonly KEvent _notificationEvent;
|
||||
private int _notificationEventHandle = 0;
|
||||
|
||||
private readonly LinkedList<NotificationInfo> _notifications;
|
||||
|
||||
private bool _hasNewFriendRequest;
|
||||
private bool _hasFriendListUpdate;
|
||||
|
||||
public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_userId = userId;
|
||||
_permissionLevel = permissionLevel;
|
||||
_notifications = new LinkedList<NotificationInfo>();
|
||||
_notificationEvent = new KEvent(context.Device.System.KernelContext);
|
||||
|
||||
_hasNewFriendRequest = false;
|
||||
_hasFriendListUpdate = false;
|
||||
|
||||
NotificationEventHandler.Instance.RegisterNotificationService(this);
|
||||
}
|
||||
|
||||
[CommandCmif(0)] //2.0.0+
|
||||
// nn::friends::detail::ipc::INotificationService::GetEvent() -> handle<copy>
|
||||
public ResultCode GetEvent(ServiceCtx context)
|
||||
{
|
||||
if (_notificationEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)] //2.0.0+
|
||||
// nn::friends::detail::ipc::INotificationService::Clear()
|
||||
public ResultCode Clear(ServiceCtx context)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_hasNewFriendRequest = false;
|
||||
_hasFriendListUpdate = false;
|
||||
|
||||
_notifications.Clear();
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)] // 2.0.0+
|
||||
// nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo
|
||||
public ResultCode Pop(ServiceCtx context)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_notifications.Count >= 1)
|
||||
{
|
||||
NotificationInfo notificationInfo = _notifications.First.Value;
|
||||
_notifications.RemoveFirst();
|
||||
|
||||
if (notificationInfo.Type == NotificationEventType.FriendListUpdate)
|
||||
{
|
||||
_hasFriendListUpdate = false;
|
||||
}
|
||||
else if (notificationInfo.Type == NotificationEventType.NewFriendRequest)
|
||||
{
|
||||
_hasNewFriendRequest = false;
|
||||
}
|
||||
|
||||
context.ResponseData.WriteStruct(notificationInfo);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode.NotificationQueueEmpty;
|
||||
}
|
||||
|
||||
public void SignalFriendListUpdate(UserId targetId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_userId == targetId)
|
||||
{
|
||||
if (!_hasFriendListUpdate)
|
||||
{
|
||||
NotificationInfo friendListNotification = new();
|
||||
|
||||
if (_notifications.Count != 0)
|
||||
{
|
||||
friendListNotification = _notifications.First.Value;
|
||||
_notifications.RemoveFirst();
|
||||
}
|
||||
|
||||
friendListNotification.Type = NotificationEventType.FriendListUpdate;
|
||||
_hasFriendListUpdate = true;
|
||||
|
||||
if (_hasNewFriendRequest)
|
||||
{
|
||||
NotificationInfo newFriendRequestNotification = new();
|
||||
|
||||
if (_notifications.Count != 0)
|
||||
{
|
||||
newFriendRequestNotification = _notifications.First.Value;
|
||||
_notifications.RemoveFirst();
|
||||
}
|
||||
|
||||
newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest;
|
||||
_notifications.AddFirst(newFriendRequestNotification);
|
||||
}
|
||||
|
||||
// We defer this to make sure we are on top of the queue.
|
||||
_notifications.AddFirst(friendListNotification);
|
||||
}
|
||||
|
||||
_notificationEvent.ReadableEvent.Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SignalNewFriendRequest(UserId targetId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if ((_permissionLevel & FriendServicePermissionLevel.ViewerMask) != 0 && _userId == targetId)
|
||||
{
|
||||
if (!_hasNewFriendRequest)
|
||||
{
|
||||
if (_notifications.Count == 100)
|
||||
{
|
||||
SignalFriendListUpdate(targetId);
|
||||
}
|
||||
|
||||
NotificationInfo newFriendRequestNotification = new()
|
||||
{
|
||||
Type = NotificationEventType.NewFriendRequest,
|
||||
};
|
||||
|
||||
_notifications.AddLast(newFriendRequestNotification);
|
||||
_hasNewFriendRequest = true;
|
||||
}
|
||||
|
||||
_notificationEvent.ReadableEvent.Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
NotificationEventHandler.Instance.UnregisterNotificationService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
||||
{
|
||||
public sealed class NotificationEventHandler
|
||||
{
|
||||
private static NotificationEventHandler _instance;
|
||||
private static readonly object _instanceLock = new();
|
||||
|
||||
private readonly INotificationService[] _registry;
|
||||
|
||||
public static NotificationEventHandler Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_instanceLock)
|
||||
{
|
||||
_instance ??= new NotificationEventHandler();
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationEventHandler()
|
||||
{
|
||||
_registry = new INotificationService[0x20];
|
||||
}
|
||||
|
||||
internal void RegisterNotificationService(INotificationService service)
|
||||
{
|
||||
// NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors.
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
if (_registry[i] == null)
|
||||
{
|
||||
_registry[i] = service;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UnregisterNotificationService(INotificationService service)
|
||||
{
|
||||
// NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors.
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
if (_registry[i] == service)
|
||||
{
|
||||
_registry[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use this when we will have enough things to go online.
|
||||
public void SignalFriendListUpdate(UserId targetId)
|
||||
{
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
_registry[i]?.SignalFriendListUpdate(targetId);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use this when we will have enough things to go online.
|
||||
public void SignalNewFriendRequest(UserId targetId)
|
||||
{
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
_registry[i]?.SignalNewFriendRequest(targetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
||||
{
|
||||
enum NotificationEventType : uint
|
||||
{
|
||||
Invalid = 0x0,
|
||||
FriendListUpdate = 0x1,
|
||||
NewFriendRequest = 0x65,
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
struct NotificationInfo
|
||||
{
|
||||
public NotificationEventType Type;
|
||||
private Array4<byte> _padding;
|
||||
public long NetworkUserIdPlaceholder;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||
{
|
||||
[Flags]
|
||||
enum FriendServicePermissionLevel
|
||||
{
|
||||
UserMask = 1,
|
||||
ViewerMask = 2,
|
||||
ManagerMask = 4,
|
||||
SystemMask = 8,
|
||||
|
||||
Administrator = -1,
|
||||
User = UserMask,
|
||||
Viewer = UserMask | ViewerMask,
|
||||
Manager = UserMask | ViewerMask | ManagerMask,
|
||||
System = UserMask | SystemMask,
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||
struct PlayHistoryRegistrationKey
|
||||
{
|
||||
public ushort Type;
|
||||
public byte KeyIndex;
|
||||
public byte UserIdBool;
|
||||
public byte UnknownBool;
|
||||
public Array11<byte> Reserved;
|
||||
public Array16<byte> Uuid;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ using LibHac;
|
|||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Path = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
@ -149,7 +150,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
|||
// Commit()
|
||||
public ResultCode Commit(ServiceCtx context)
|
||||
{
|
||||
return (ResultCode)_fileSystem.Get.Commit().Value;
|
||||
ResultCode resultCode = (ResultCode)_fileSystem.Get.Commit().Value;
|
||||
if (resultCode == ResultCode.PathAlreadyInUse)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceFs, "The file system is already in use by another process.");
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
|
|
65
src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs
Normal file
65
src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
{
|
||||
class LazyFile : LibHac.Fs.Fsa.IFile
|
||||
{
|
||||
private readonly LibHac.Fs.Fsa.IFileSystem _fs;
|
||||
private readonly string _filePath;
|
||||
private readonly UniqueRef<LibHac.Fs.Fsa.IFile> _fileReference = new();
|
||||
private readonly FileInfo _fileInfo;
|
||||
|
||||
public LazyFile(string filePath, string prefix, LibHac.Fs.Fsa.IFileSystem fs)
|
||||
{
|
||||
_fs = fs;
|
||||
_filePath = filePath;
|
||||
_fileInfo = new FileInfo(prefix + "/" + filePath);
|
||||
}
|
||||
|
||||
private void PrepareFile()
|
||||
{
|
||||
if (_fileReference.Get == null)
|
||||
{
|
||||
_fs.OpenFile(ref _fileReference.Ref, _filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||
{
|
||||
PrepareFile();
|
||||
|
||||
return _fileReference.Get!.Read(out bytesRead, offset, destination);
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Result DoFlush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
size = _fileInfo.Length;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||
newState.Buttons = (MouseButton)buttons;
|
||||
newState.X = mouseX;
|
||||
newState.Y = mouseY;
|
||||
newState.DeltaX = mouseX - previousEntry.DeltaX;
|
||||
newState.DeltaY = mouseY - previousEntry.DeltaY;
|
||||
newState.DeltaX = mouseX - previousEntry.X;
|
||||
newState.DeltaY = mouseY - previousEntry.Y;
|
||||
newState.WheelDeltaX = scrollX;
|
||||
newState.WheelDeltaY = scrollY;
|
||||
newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None;
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||
|
||||
private bool _sixAxisSensorFusionEnabled;
|
||||
private bool _unintendedHomeButtonInputProtectionEnabled;
|
||||
private bool _npadAnalogStickCenterClampEnabled;
|
||||
private bool _vibrationPermitted;
|
||||
private bool _usbFullKeyControllerEnabled;
|
||||
private readonly bool _isFirmwareUpdateAvailableForSixAxisSensor;
|
||||
|
@ -1107,6 +1108,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||
// If not, it returns nothing.
|
||||
}
|
||||
|
||||
[CommandCmif(134)] // 6.1.0+
|
||||
// SetNpadUseAnalogStickUseCenterClamp(bool Enable, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetNpadUseAnalogStickUseCenterClamp(ServiceCtx context)
|
||||
{
|
||||
ulong pid = context.RequestData.ReadUInt64();
|
||||
_npadAnalogStickCenterClampEnabled = context.RequestData.ReadUInt32() != 0;
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, _npadAnalogStickCenterClampEnabled });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(200)]
|
||||
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
|
||||
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
|
||||
|
@ -1821,5 +1835,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1004)] // 17.0.0+
|
||||
// SetTouchScreenResolution(int width, int height, nn::applet::AppletResourceUserId)
|
||||
public ResultCode SetTouchScreenResolution(ServiceCtx context)
|
||||
{
|
||||
int width = context.RequestData.ReadInt32();
|
||||
int height = context.RequestData.ReadInt32();
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { width, height, appletResourceUserId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,5 +40,12 @@ namespace Ryujinx.HLE.HOS.Services.Nim
|
|||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)] // 17.0.0+
|
||||
// CreateServerInterface2(pid, handle<unknown>, u64) -> object<nn::ec::IshopServiceAccessServer>
|
||||
public ResultCode CreateServerInterface2(ServiceCtx context)
|
||||
{
|
||||
return CreateServerInterface(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Host1x;
|
||||
using Ryujinx.Graphics.Nvdec;
|
||||
using Ryujinx.Graphics.Vic;
|
||||
|
@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
{
|
||||
class Host1xContext : IDisposable
|
||||
{
|
||||
public MemoryManager Smmu { get; }
|
||||
public DeviceMemoryManager Smmu { get; }
|
||||
public NvMemoryAllocator MemoryAllocator { get; }
|
||||
public Host1xDevice Host1x { get; }
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv
|
|||
{
|
||||
MemoryAllocator = new NvMemoryAllocator();
|
||||
Host1x = new Host1xDevice(gpu.Synchronization);
|
||||
Smmu = gpu.CreateMemoryManager(pid);
|
||||
Smmu = gpu.CreateDeviceMemoryManager(pid);
|
||||
var nvdec = new NvdecDevice(Smmu);
|
||||
var vic = new VicDevice(Smmu);
|
||||
Host1x.RegisterDevice(ClassId.Nvdec, nvdec);
|
||||
|
|
|
@ -266,7 +266,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
|||
|
||||
if (size == 0)
|
||||
{
|
||||
size = (uint)map.Size;
|
||||
size = map.Size;
|
||||
}
|
||||
|
||||
NvInternalResult result = NvInternalResult.Success;
|
||||
|
|
|
@ -250,12 +250,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
|||
{
|
||||
if (map.DmaMapAddress == 0)
|
||||
{
|
||||
ulong va = _host1xContext.MemoryAllocator.GetFreeAddress((ulong)map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize);
|
||||
ulong va = _host1xContext.MemoryAllocator.GetFreeAddress(map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize);
|
||||
|
||||
if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + (uint)map.Size) <= uint.MaxValue)
|
||||
if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + map.Size) <= uint.MaxValue)
|
||||
{
|
||||
_host1xContext.MemoryAllocator.AllocateRange(va, (uint)map.Size, freeAddressStartPosition);
|
||||
_host1xContext.Smmu.Map(map.Address, va, (uint)map.Size, PteKind.Pitch); // FIXME: This should not use the GMMU.
|
||||
_host1xContext.MemoryAllocator.AllocateRange(va, map.Size, freeAddressStartPosition);
|
||||
_host1xContext.Smmu.Map(map.Address, va, map.Size);
|
||||
map.DmaMapAddress = va;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -50,6 +50,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
|
|||
case 0x06:
|
||||
result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments);
|
||||
break;
|
||||
case 0x12:
|
||||
result = CallIoctlMethod<NumVsmsArguments>(NumVsms, arguments);
|
||||
break;
|
||||
case 0x13:
|
||||
result = CallIoctlMethod<VsmsMappingArguments>(VsmsMapping, arguments);
|
||||
break;
|
||||
case 0x14:
|
||||
result = CallIoctlMethod<GetActiveSlotMaskArguments>(GetActiveSlotMask, arguments);
|
||||
break;
|
||||
|
@ -76,6 +82,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
|
|||
case 0x06:
|
||||
result = CallIoctlMethod<GetTpcMasksArguments, int>(GetTpcMasks, arguments, inlineOutBuffer);
|
||||
break;
|
||||
case 0x12:
|
||||
result = CallIoctlMethod<NumVsmsArguments>(NumVsms, arguments);
|
||||
break;
|
||||
case 0x13:
|
||||
result = CallIoctlMethod<VsmsMappingArguments>(VsmsMapping, arguments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,6 +228,27 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu
|
|||
return NvInternalResult.Success;
|
||||
}
|
||||
|
||||
private NvInternalResult NumVsms(ref NumVsmsArguments arguments)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNv);
|
||||
|
||||
arguments.NumVsms = 2;
|
||||
|
||||
return NvInternalResult.Success;
|
||||
}
|
||||
|
||||
private NvInternalResult VsmsMapping(ref VsmsMappingArguments arguments)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNv);
|
||||
|
||||
arguments.Sm0GpcIndex = 0;
|
||||
arguments.Sm0TpcIndex = 0;
|
||||
arguments.Sm1GpcIndex = 0;
|
||||
arguments.Sm1TpcIndex = 1;
|
||||
|
||||
return NvInternalResult.Success;
|
||||
}
|
||||
|
||||
private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNv);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct NumVsmsArguments
|
||||
{
|
||||
public uint NumVsms;
|
||||
public uint Reserved;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct VsmsMappingArguments
|
||||
{
|
||||
public byte Sm0GpcIndex;
|
||||
public byte Sm0TpcIndex;
|
||||
public byte Sm1GpcIndex;
|
||||
public byte Sm1TpcIndex;
|
||||
public uint Reserved;
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
return NvInternalResult.InvalidInput;
|
||||
}
|
||||
|
||||
int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize);
|
||||
uint size = BitUtils.AlignUp(arguments.Size, (uint)MemoryManager.PageSize);
|
||||
|
||||
arguments.Handle = CreateHandleFromMap(new NvMapHandle(size));
|
||||
|
||||
|
@ -128,7 +128,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
map.Align = arguments.Align;
|
||||
map.Kind = (byte)arguments.Kind;
|
||||
|
||||
int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize);
|
||||
uint size = BitUtils.AlignUp(map.Size, (uint)MemoryManager.PageSize);
|
||||
|
||||
ulong address = arguments.Address;
|
||||
|
||||
|
@ -191,7 +191,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
switch (arguments.Param)
|
||||
{
|
||||
case NvMapHandleParam.Size:
|
||||
arguments.Result = map.Size;
|
||||
arguments.Result = (int)map.Size;
|
||||
break;
|
||||
case NvMapHandleParam.Align:
|
||||
arguments.Result = map.Align;
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct NvMapCreate
|
||||
{
|
||||
public int Size;
|
||||
public uint Size;
|
||||
public int Handle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
public int Handle;
|
||||
public int Padding;
|
||||
public ulong Address;
|
||||
public int Size;
|
||||
public uint Size;
|
||||
public int Flags;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
public int Handle;
|
||||
public int Id;
|
||||
#pragma warning restore CS0649
|
||||
public int Size;
|
||||
public uint Size;
|
||||
public int Align;
|
||||
public int Kind;
|
||||
public ulong Address;
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap
|
|||
_dupes = 1;
|
||||
}
|
||||
|
||||
public NvMapHandle(int size) : this()
|
||||
public NvMapHandle(uint size) : this()
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
|
|
@ -159,9 +159,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
|||
}
|
||||
else
|
||||
{
|
||||
#pragma warning disable CS0162 // Unreachable code
|
||||
return ResultCode.StereoVisionRestrictionConfigurableDisabled;
|
||||
#pragma warning restore CS0162
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Ptm.Ts.Types;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ptm.Ts
|
||||
{
|
||||
[Service("ts")]
|
||||
class IMeasurementServer : IpcService
|
||||
{
|
||||
private const uint DefaultTemperature = 42u;
|
||||
|
||||
public IMeasurementServer(ServiceCtx context) { }
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetTemperature(Location location) -> u32
|
||||
public ResultCode GetTemperature(ServiceCtx context)
|
||||
{
|
||||
Location location = (Location)context.RequestData.ReadByte();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location });
|
||||
|
||||
context.ResponseData.Write(DefaultTemperature);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// GetTemperatureMilliC(Location location) -> u32
|
||||
public ResultCode GetTemperatureMilliC(ServiceCtx context)
|
||||
{
|
||||
Location location = (Location)context.RequestData.ReadByte();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location });
|
||||
|
||||
context.ResponseData.Write(DefaultTemperature * 1000);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace Ryujinx.HLE.HOS.Services.Ptm.Ts.Types
|
||||
{
|
||||
enum Location : byte
|
||||
{
|
||||
Internal,
|
||||
External,
|
||||
}
|
||||
}
|
|
@ -287,6 +287,10 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
_wakeEvent.WritableEvent.Clear();
|
||||
}
|
||||
}
|
||||
else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null)
|
||||
{
|
||||
DestroySession(handles[signaledIndex]);
|
||||
}
|
||||
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||
|
@ -299,6 +303,16 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
Dispose();
|
||||
}
|
||||
|
||||
private void DestroySession(int serverSessionHandle)
|
||||
{
|
||||
_context.Syscall.CloseHandle(serverSessionHandle);
|
||||
|
||||
if (RemoveSessionObj(serverSessionHandle, out var session))
|
||||
{
|
||||
(session as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Process(int serverSessionHandle, ulong recvListAddr)
|
||||
{
|
||||
IpcMessage request = ReadRequest();
|
||||
|
@ -360,7 +374,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
response.RawData = _responseDataStream.ToArray();
|
||||
}
|
||||
else if (request.Type == IpcMessageType.CmifControl ||
|
||||
request.Type == IpcMessageType.CmifControlWithContext)
|
||||
request.Type == IpcMessageType.CmifControlWithContext)
|
||||
{
|
||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||
uint magic = (uint)_requestDataReader.ReadUInt64();
|
||||
|
@ -412,11 +426,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
}
|
||||
else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
||||
{
|
||||
_context.Syscall.CloseHandle(serverSessionHandle);
|
||||
if (RemoveSessionObj(serverSessionHandle, out var session))
|
||||
{
|
||||
(session as IDisposable)?.Dispose();
|
||||
}
|
||||
DestroySession(serverSessionHandle);
|
||||
shouldReply = false;
|
||||
}
|
||||
// If the type is past 0xF, we are using TIPC
|
||||
|
@ -464,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||
{
|
||||
const int MessageSize = 0x100;
|
||||
|
||||
using IMemoryOwner<byte> reqDataOwner = ByteMemoryPool.Rent(MessageSize);
|
||||
using SpanOwner<byte> reqDataOwner = SpanOwner<byte>.Rent(MessageSize);
|
||||
|
||||
Span<byte> reqDataSpan = reqDataOwner.Memory.Span;
|
||||
Span<byte> reqDataSpan = reqDataOwner.Span;
|
||||
|
||||
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);
|
||||
|
||||
|
|
|
@ -121,7 +121,14 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||
{
|
||||
IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint;
|
||||
|
||||
context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint));
|
||||
if (endPoint != null)
|
||||
{
|
||||
context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Memory.Write(bufferPosition, new BsdSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
|
@ -433,8 +440,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||
|
||||
// If we are here, that mean nothing was available, sleep for 50ms
|
||||
context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000);
|
||||
context.Thread.HandlePostSyscall();
|
||||
}
|
||||
while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds);
|
||||
while (context.Thread.Context.Running && PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds);
|
||||
}
|
||||
else if (timeout == -1)
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl;
|
|||
using Ryujinx.HLE.HOS.Services.Ssl.Types;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
|
@ -83,10 +84,40 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
|||
}
|
||||
#pragma warning restore SYSLIB0039
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the hostname of the current remote in case the provided hostname is null or empty.
|
||||
/// </summary>
|
||||
/// <param name="hostName">The current hostname</param>
|
||||
/// <returns>Either the resolved or provided hostname</returns>
|
||||
/// <remarks>
|
||||
/// This is done to avoid getting an <see cref="System.Security.Authentication.AuthenticationException"/>
|
||||
/// as the remote certificate will be rejected with <c>RemoteCertificateNameMismatch</c> due to an empty hostname.
|
||||
/// This is not what the switch does!
|
||||
/// It might just skip remote hostname verification if the hostname wasn't set with <see cref="ISslConnection.SetHostName"/> before.
|
||||
/// TODO: Remove this as soon as we know how the switch deals with empty hostnames
|
||||
/// </remarks>
|
||||
private string RetrieveHostName(string hostName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(hostName))
|
||||
{
|
||||
return hostName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Dns.GetHostEntry(Socket.RemoteEndPoint.Address).HostName;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return hostName;
|
||||
}
|
||||
}
|
||||
|
||||
public ResultCode Handshake(string hostName)
|
||||
{
|
||||
StartSslOperation();
|
||||
_stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null);
|
||||
hostName = RetrieveHostName(hostName);
|
||||
_stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false);
|
||||
EndSslOperation();
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
|
||||
{
|
||||
Parcel inputParcelReader = new(inputParcel.ToArray());
|
||||
using Parcel inputParcelReader = new(inputParcel);
|
||||
|
||||
// TODO: support objects?
|
||||
Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
|
||||
using Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
|
||||
|
||||
string inputInterfaceToken = inputParcelReader.ReadInterfaceToken();
|
||||
|
||||
|
|
|
@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
||||
|
||||
using IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.RentCleared(replySize);
|
||||
using SpanOwner<byte> outputParcelOwner = SpanOwner<byte>.RentCleared(checked((int)replySize));
|
||||
|
||||
Span<byte> outputParcel = outputParcelOwner.Memory.Span;
|
||||
Span<byte> outputParcel = outputParcelOwner.Span;
|
||||
|
||||
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
|
@ -9,13 +10,13 @@ using System.Text;
|
|||
|
||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
class Parcel
|
||||
sealed class Parcel : IDisposable
|
||||
{
|
||||
private readonly byte[] _rawData;
|
||||
private readonly MemoryOwner<byte> _rawDataOwner;
|
||||
|
||||
private Span<byte> Raw => new(_rawData);
|
||||
private Span<byte> Raw => _rawDataOwner.Span;
|
||||
|
||||
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0];
|
||||
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];
|
||||
|
||||
private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize);
|
||||
|
||||
|
@ -24,9 +25,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
private int _payloadPosition;
|
||||
private int _objectPosition;
|
||||
|
||||
public Parcel(byte[] rawData)
|
||||
private bool _isDisposed;
|
||||
|
||||
public Parcel(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_rawData = rawData;
|
||||
_rawDataOwner = MemoryOwner<byte>.RentCopy(data);
|
||||
|
||||
_payloadPosition = 0;
|
||||
_objectPosition = 0;
|
||||
|
@ -36,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
{
|
||||
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
||||
|
||||
_rawData = new byte[BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)];
|
||||
_rawDataOwner = MemoryOwner<byte>.RentCleared(checked((int)BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)));
|
||||
|
||||
Header.PayloadSize = payloadSize;
|
||||
Header.ObjectsSize = objectsSize;
|
||||
|
@ -132,7 +135,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
// TODO: figure out what this value is
|
||||
|
||||
WriteInplaceObject(new byte[4] { 0, 0, 0, 0 });
|
||||
Span<byte> fourBytes = stackalloc byte[4];
|
||||
|
||||
WriteInplaceObject(fourBytes);
|
||||
}
|
||||
|
||||
public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
|
||||
|
@ -219,5 +224,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>())];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_isDisposed = true;
|
||||
|
||||
_rawDataOwner.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -412,9 +412,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat);
|
||||
|
||||
int bytesPerPixel =
|
||||
byte bytesPerPixel =
|
||||
format == Format.B5G6R5Unorm ||
|
||||
format == Format.R4G4B4A4Unorm ? 2 : 4;
|
||||
format == Format.R4G4B4A4Unorm ? (byte)2 : (byte)4;
|
||||
|
||||
int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
|||
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService;
|
||||
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Vi.Types;
|
||||
using Ryujinx.HLE.Ui;
|
||||
using Ryujinx.HLE.UI;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -250,7 +250,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
Parcel parcel = new(0x28, 0x4);
|
||||
using Parcel parcel = new(0x28, 0x4);
|
||||
|
||||
parcel.WriteObject(producer, "dispdrv\0");
|
||||
|
||||
|
@ -288,7 +288,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
|||
|
||||
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
|
||||
|
||||
Parcel parcel = new(0x28, 0x4);
|
||||
using Parcel parcel = new(0x28, 0x4);
|
||||
|
||||
parcel.WriteObject(producer, "dispdrv\0");
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue