Move solution and projects to src

This commit is contained in:
TSR Berry 2023-04-08 01:22:00 +02:00 committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View file

@ -0,0 +1,241 @@
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
public class AccountManager
{
public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
private readonly AccountSaveDataManager _accountSaveDataManager;
// Todo: The account service doesn't have the permissions to delete save data. Qlaunch takes care of deleting
// save data, so we're currently passing a client with full permissions. Consider moving save data deletion
// outside of the AccountManager.
private readonly HorizonClient _horizonClient;
private readonly ConcurrentDictionary<string, UserProfile> _profiles;
private UserProfile[] _storedOpenedUsers;
public UserProfile LastOpenedUser { get; private set; }
public AccountManager(HorizonClient horizonClient, string initialProfileName = null)
{
_horizonClient = horizonClient;
_profiles = new ConcurrentDictionary<string, UserProfile>();
_storedOpenedUsers = Array.Empty<UserProfile>();
_accountSaveDataManager = new AccountSaveDataManager(_profiles);
if (!_profiles.TryGetValue(DefaultUserId.ToString(), out _))
{
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
AddUser("RyuPlayer", defaultUserImage, DefaultUserId);
OpenUser(DefaultUserId);
}
else
{
UserId commandLineUserProfileOverride = default;
if (!string.IsNullOrEmpty(initialProfileName))
{
commandLineUserProfileOverride = _profiles.Values.FirstOrDefault(x => x.Name == initialProfileName)?.UserId ?? default;
if (commandLineUserProfileOverride.IsNull)
Logger.Warning?.Print(LogClass.Application, $"The command line specified profile named '{initialProfileName}' was not found");
}
OpenUser(commandLineUserProfileOverride.IsNull ? _accountSaveDataManager.LastOpened : commandLineUserProfileOverride);
}
}
public void AddUser(string name, byte[] image, UserId userId = new UserId())
{
if (userId.IsNull)
{
userId = new UserId(Guid.NewGuid().ToString().Replace("-", ""));
}
UserProfile profile = new UserProfile(userId, name, image);
_profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
_accountSaveDataManager.Save(_profiles);
}
public void OpenUser(UserId userId)
{
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
{
// TODO: Support multiple open users ?
foreach (UserProfile userProfile in GetAllUsers())
{
if (userProfile == LastOpenedUser)
{
userProfile.AccountState = AccountState.Closed;
break;
}
}
(LastOpenedUser = profile).AccountState = AccountState.Open;
_accountSaveDataManager.LastOpened = userId;
}
_accountSaveDataManager.Save(_profiles);
}
public void CloseUser(UserId userId)
{
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
{
profile.AccountState = AccountState.Closed;
}
_accountSaveDataManager.Save(_profiles);
}
public void OpenUserOnlinePlay(UserId userId)
{
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
{
// TODO: Support multiple open online users ?
foreach (UserProfile userProfile in GetAllUsers())
{
if (userProfile == LastOpenedUser)
{
userProfile.OnlinePlayState = AccountState.Closed;
break;
}
}
profile.OnlinePlayState = AccountState.Open;
}
_accountSaveDataManager.Save(_profiles);
}
public void CloseUserOnlinePlay(UserId userId)
{
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
{
profile.OnlinePlayState = AccountState.Closed;
}
_accountSaveDataManager.Save(_profiles);
}
public void SetUserImage(UserId userId, byte[] image)
{
foreach (UserProfile userProfile in GetAllUsers())
{
if (userProfile.UserId == userId)
{
userProfile.Image = image;
break;
}
}
_accountSaveDataManager.Save(_profiles);
}
public void SetUserName(UserId userId, string name)
{
foreach (UserProfile userProfile in GetAllUsers())
{
if (userProfile.UserId == userId)
{
userProfile.Name = name;
break;
}
}
_accountSaveDataManager.Save(_profiles);
}
public void DeleteUser(UserId userId)
{
DeleteSaveData(userId);
_profiles.Remove(userId.ToString(), out _);
OpenUser(DefaultUserId);
_accountSaveDataManager.Save(_profiles);
}
private void DeleteSaveData(UserId userId)
{
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default,
new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
while (true)
{
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
if (readCount == 0)
{
break;
}
for (int i = 0; i < readCount; i++)
{
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId).ThrowIfFailure();
}
}
}
internal int GetUserCount()
{
return _profiles.Count;
}
internal bool TryGetUser(UserId userId, out UserProfile profile)
{
return _profiles.TryGetValue(userId.ToString(), out profile);
}
public IEnumerable<UserProfile> GetAllUsers()
{
return _profiles.Values;
}
internal IEnumerable<UserProfile> GetOpenedUsers()
{
return _profiles.Values.Where(x => x.AccountState == AccountState.Open);
}
internal IEnumerable<UserProfile> GetStoredOpenedUsers()
{
return _storedOpenedUsers;
}
internal void StoreOpenedUsers()
{
_storedOpenedUsers = _profiles.Values.Where(x => x.AccountState == AccountState.Open).ToArray();
}
internal UserProfile GetFirst()
{
return _profiles.First().Value;
}
}
}

View file

@ -0,0 +1,76 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Account.Acc.Types;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class AccountSaveDataManager
{
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly ProfilesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public UserId LastOpened { get; set; }
public AccountSaveDataManager(ConcurrentDictionary<string, UserProfile> profiles)
{
// TODO: Use 0x8000000000000010 system savedata instead of a JSON file if needed.
if (File.Exists(_profilesJsonPath))
{
try
{
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, SerializerContext.ProfilesJson);
foreach (var profile in profilesJson.Profiles)
{
UserProfile addedProfile = new UserProfile(new UserId(profile.UserId), profile.Name, profile.Image, profile.LastModifiedTimestamp);
profiles.AddOrUpdate(profile.UserId, addedProfile, (key, old) => addedProfile);
}
LastOpened = new UserId(profilesJson.LastOpened);
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, $"Failed to parse {_profilesJsonPath}: {e.Message} Loading default profile!");
LastOpened = AccountManager.DefaultUserId;
}
}
else
{
LastOpened = AccountManager.DefaultUserId;
}
}
public void Save(ConcurrentDictionary<string, UserProfile> profiles)
{
ProfilesJson profilesJson = new ProfilesJson()
{
Profiles = new List<UserProfileJson>(),
LastOpened = LastOpened.ToString()
};
foreach (var profile in profiles)
{
profilesJson.Profiles.Add(new UserProfileJson()
{
UserId = profile.Value.UserId.ToString(),
Name = profile.Value.Name,
AccountState = profile.Value.AccountState,
OnlinePlayState = profile.Value.OnlinePlayState,
LastModifiedTimestamp = profile.Value.LastModifiedTimestamp,
Image = profile.Value.Image,
});
}
JsonHelper.SerializeToFile(_profilesJsonPath, profilesJson, SerializerContext.ProfilesJson);
}
}
}

View file

@ -0,0 +1,75 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IManagerForApplication : IpcService
{
private ManagerServer _managerServer;
public IManagerForApplication(UserId userId)
{
_managerServer = new ManagerServer(userId);
}
[CommandCmif(0)]
// CheckAvailability()
public ResultCode CheckAvailability(ServiceCtx context)
{
return _managerServer.CheckAvailability(context);
}
[CommandCmif(1)]
// GetAccountId() -> nn::account::NetworkServiceAccountId
public ResultCode GetAccountId(ServiceCtx context)
{
return _managerServer.GetAccountId(context);
}
[CommandCmif(2)]
// EnsureIdTokenCacheAsync() -> object<nn::account::detail::IAsyncContext>
public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context)
{
ResultCode resultCode = _managerServer.EnsureIdTokenCacheAsync(context, out IAsyncContext asyncContext);
if (resultCode == ResultCode.Success)
{
MakeObject(context, asyncContext);
}
return resultCode;
}
[CommandCmif(3)]
// LoadIdTokenCache() -> (u32 id_token_cache_size, buffer<bytes, 6>)
public ResultCode LoadIdTokenCache(ServiceCtx context)
{
return _managerServer.LoadIdTokenCache(context);
}
[CommandCmif(130)]
// GetNintendoAccountUserResourceCacheForApplication() -> (nn::account::NintendoAccountId, nn::account::nas::NasUserBaseForApplication, buffer<bytes, 6>)
public ResultCode GetNintendoAccountUserResourceCacheForApplication(ServiceCtx context)
{
return _managerServer.GetNintendoAccountUserResourceCacheForApplication(context);
}
[CommandCmif(160)] // 5.0.0+
// StoreOpenContext()
public ResultCode StoreOpenContext(ServiceCtx context)
{
return _managerServer.StoreOpenContext(context);
}
[CommandCmif(170)] // 6.0.0+
// LoadNetworkServiceLicenseKindAsync() -> object<nn::account::detail::IAsyncNetworkServiceLicenseKindContext>
public ResultCode LoadNetworkServiceLicenseKindAsync(ServiceCtx context)
{
ResultCode resultCode = _managerServer.LoadNetworkServiceLicenseKindAsync(context, out IAsyncNetworkServiceLicenseKindContext asyncContext);
if (resultCode == ResultCode.Success)
{
MakeObject(context, asyncContext);
}
return resultCode;
}
}
}

View file

@ -0,0 +1,47 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IManagerForSystemService : IpcService
{
private ManagerServer _managerServer;
public IManagerForSystemService(UserId userId)
{
_managerServer = new ManagerServer(userId);
}
[CommandCmif(0)]
// CheckAvailability()
public ResultCode CheckAvailability(ServiceCtx context)
{
return _managerServer.CheckAvailability(context);
}
[CommandCmif(1)]
// GetAccountId() -> nn::account::NetworkServiceAccountId
public ResultCode GetAccountId(ServiceCtx context)
{
return _managerServer.GetAccountId(context);
}
[CommandCmif(2)]
// EnsureIdTokenCacheAsync() -> object<nn::account::detail::IAsyncContext>
public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context)
{
ResultCode resultCode = _managerServer.EnsureIdTokenCacheAsync(context, out IAsyncContext asyncContext);
if (resultCode == ResultCode.Success)
{
MakeObject(context, asyncContext);
}
return resultCode;
}
[CommandCmif(3)]
// LoadIdTokenCache() -> (u32 id_token_cache_size, buffer<bytes, 6>)
public ResultCode LoadIdTokenCache(ServiceCtx context)
{
return _managerServer.LoadIdTokenCache(context);
}
}
}

View file

@ -0,0 +1,40 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IProfile : IpcService
{
private ProfileServer _profileServer;
public IProfile(UserProfile profile)
{
_profileServer = new ProfileServer(profile);
}
[CommandCmif(0)]
// Get() -> (nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x1a>)
public ResultCode Get(ServiceCtx context)
{
return _profileServer.Get(context);
}
[CommandCmif(1)]
// GetBase() -> nn::account::profile::ProfileBase
public ResultCode GetBase(ServiceCtx context)
{
return _profileServer.GetBase(context);
}
[CommandCmif(10)]
// GetImageSize() -> u32
public ResultCode GetImageSize(ServiceCtx context)
{
return _profileServer.GetImageSize(context);
}
[CommandCmif(11)]
// LoadImage() -> (u32, buffer<bytes, 6>)
public ResultCode LoadImage(ServiceCtx context)
{
return _profileServer.LoadImage(context);
}
}
}

View file

@ -0,0 +1,54 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IProfileEditor : IpcService
{
private ProfileServer _profileServer;
public IProfileEditor(UserProfile profile)
{
_profileServer = new ProfileServer(profile);
}
[CommandCmif(0)]
// Get() -> (nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x1a>)
public ResultCode Get(ServiceCtx context)
{
return _profileServer.Get(context);
}
[CommandCmif(1)]
// GetBase() -> nn::account::profile::ProfileBase
public ResultCode GetBase(ServiceCtx context)
{
return _profileServer.GetBase(context);
}
[CommandCmif(10)]
// GetImageSize() -> u32
public ResultCode GetImageSize(ServiceCtx context)
{
return _profileServer.GetImageSize(context);
}
[CommandCmif(11)]
// LoadImage() -> (u32, buffer<bytes, 6>)
public ResultCode LoadImage(ServiceCtx context)
{
return _profileServer.LoadImage(context);
}
[CommandCmif(100)]
// Store(nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x19>)
public ResultCode Store(ServiceCtx context)
{
return _profileServer.Store(context);
}
[CommandCmif(101)]
// StoreWithImage(nn::account::profile::ProfileBase, buffer<nn::account::profile::UserData, 0x19>, buffer<bytes, 5>)
public ResultCode StoreWithImage(ServiceCtx context)
{
return _profileServer.StoreWithImage(context);
}
}
}

View file

@ -0,0 +1,187 @@
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.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class ManagerServer
{
// TODO: Determine where and how NetworkServiceAccountId is set.
private const long NetworkServiceAccountId = 0xcafe;
private UserId _userId;
public ManagerServer(UserId userId)
{
_userId = userId;
}
private static string GenerateIdToken()
{
using RSA provider = RSA.Create(2048);
RSAParameters parameters = provider.ExportParameters(true);
RsaSecurityKey secKey = new RsaSecurityKey(parameters);
SigningCredentials credentials = new SigningCredentials(secKey, "RS256");
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);
byte[] deviceId = new byte[0x10];
RandomNumberGenerator.Fill(deviceId);
byte[] deviceAccountId = new byte[0x10];
RandomNumberGenerator.Fill(deviceId);
var payload = new JwtPayload
{
{ "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() }
};
JwtSecurityToken securityToken = new JwtSecurityToken(header, payload);
return new JwtSecurityTokenHandler().WriteToken(securityToken);
}
public ResultCode CheckAvailability(ServiceCtx context)
{
// NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x".
// Then it searches the Availability of Online Services related to the UserId in this file and returns it.
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
// NOTE: Even if we try to return different error codes here, the guest still needs other calls.
return ResultCode.Success;
}
public ResultCode GetAccountId(ServiceCtx context)
{
// NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted
// as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the account:/ savedata.
// Then it searches the NetworkServiceAccountId related to the UserId in this file and returns it.
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { NetworkServiceAccountId });
context.ResponseData.Write(NetworkServiceAccountId);
return ResultCode.Success;
}
public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context, out IAsyncContext asyncContext)
{
KEvent asyncEvent = new KEvent(context.Device.System.KernelContext);
AsyncExecution asyncExecution = new AsyncExecution(asyncEvent);
asyncExecution.Initialize(1000, EnsureIdTokenCacheAsyncImpl);
asyncContext = new IAsyncContext(asyncExecution);
// return ResultCode.NullObject if the IAsyncContext pointer is null. Doesn't occur in our case.
return ResultCode.Success;
}
private async Task EnsureIdTokenCacheAsyncImpl(CancellationToken token)
{
// NOTE: This open the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x")
// in the "account:/" savedata.
// Then its read data, use dauth API with this data to get the Token Id and probably store the dauth response
// in "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the "account:/" savedata.
// Since we don't support online services, we can stub it.
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
// TODO: Use a real function instead, with the CancellationToken.
await Task.CompletedTask;
}
public ResultCode LoadIdTokenCache(ServiceCtx context)
{
ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
ulong bufferSize = context.Request.ReceiveBuff[0].Size;
// NOTE: This opens the file at "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x")
// in the "account:/" savedata and writes some data in the buffer.
// Since we don't support online services, we can stub it.
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
/*
if (internal_object != null)
{
if (bufferSize > 0xC00)
{
return ResultCode.InvalidIdTokenCacheBufferSize;
}
}
*/
byte[] tokenData = Encoding.ASCII.GetBytes(GenerateIdToken());
context.Memory.Write(bufferPosition, tokenData);
context.ResponseData.Write(tokenData.Length);
return ResultCode.Success;
}
public ResultCode GetNintendoAccountUserResourceCacheForApplication(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { NetworkServiceAccountId });
context.ResponseData.Write(NetworkServiceAccountId);
// TODO: determine and fill the output IPC buffer.
return ResultCode.Success;
}
public ResultCode StoreOpenContext(ServiceCtx context)
{
context.Device.System.AccountManager.StoreOpenedUsers();
return ResultCode.Success;
}
public ResultCode LoadNetworkServiceLicenseKindAsync(ServiceCtx context, out IAsyncNetworkServiceLicenseKindContext asyncContext)
{
KEvent asyncEvent = new KEvent(context.Device.System.KernelContext);
AsyncExecution asyncExecution = new AsyncExecution(asyncEvent);
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
// NOTE: This is an extension of the data retrieved from the id token cache.
asyncExecution.Initialize(1000, EnsureIdTokenCacheAsyncImpl);
asyncContext = new IAsyncNetworkServiceLicenseKindContext(asyncExecution, NetworkServiceLicenseKind.Subscribed);
// return ResultCode.NullObject if the IAsyncNetworkServiceLicenseKindContext pointer is null. Doesn't occur in our case.
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,114 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.Utilities;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class ProfileServer
{
private UserProfile _profile;
public ProfileServer(UserProfile profile)
{
_profile = profile;
}
public ResultCode Get(ServiceCtx context)
{
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x80UL);
ulong bufferPosition = context.Request.RecvListBuff[0].Position;
MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x80);
// TODO: Determine the struct.
context.Memory.Write(bufferPosition, 0); // Unknown
context.Memory.Write(bufferPosition + 4, 1); // Icon ID. 0 = Mii, the rest are character icon IDs.
context.Memory.Write(bufferPosition + 8, (byte)1); // Profile icon background color ID
// 0x07 bytes - Unknown
// 0x10 bytes - Some ID related to the Mii? All zeros when a character icon is used.
// 0x60 bytes - Usually zeros?
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
return GetBase(context);
}
public ResultCode GetBase(ServiceCtx context)
{
_profile.UserId.Write(context.ResponseData);
context.ResponseData.Write(_profile.LastModifiedTimestamp);
byte[] username = StringUtils.GetFixedLengthBytes(_profile.Name, 0x20, Encoding.UTF8);
context.ResponseData.Write(username);
return ResultCode.Success;
}
public ResultCode GetImageSize(ServiceCtx context)
{
context.ResponseData.Write(_profile.Image.Length);
return ResultCode.Success;
}
public ResultCode LoadImage(ServiceCtx context)
{
ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
ulong bufferLen = context.Request.ReceiveBuff[0].Size;
if ((ulong)_profile.Image.Length > bufferLen)
{
return ResultCode.InvalidBufferSize;
}
context.Memory.Write(bufferPosition, _profile.Image);
context.ResponseData.Write(_profile.Image.Length);
return ResultCode.Success;
}
public ResultCode Store(ServiceCtx context)
{
ulong userDataPosition = context.Request.PtrBuff[0].Position;
ulong userDataSize = context.Request.PtrBuff[0].Size;
byte[] userData = new byte[userDataSize];
context.Memory.Read(userDataPosition, userData);
// TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata.
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize });
return ResultCode.Success;
}
public ResultCode StoreWithImage(ServiceCtx context)
{
ulong userDataPosition = context.Request.PtrBuff[0].Position;
ulong userDataSize = context.Request.PtrBuff[0].Size;
byte[] userData = new byte[userDataSize];
context.Memory.Read(userDataPosition, userData);
ulong profileImagePosition = context.Request.SendBuff[0].Position;
ulong profileImageSize = context.Request.SendBuff[0].Size;
byte[] profileImageData = new byte[profileImageSize];
context.Memory.Read(profileImagePosition, profileImageData);
// TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata.
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize, profileImageSize });
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,254 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService;
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class ApplicationServiceServer
{
readonly AccountServiceFlag _serviceFlag;
public ApplicationServiceServer(AccountServiceFlag serviceFlag)
{
_serviceFlag = serviceFlag;
}
public ResultCode GetUserCountImpl(ServiceCtx context)
{
context.ResponseData.Write(context.Device.System.AccountManager.GetUserCount());
return ResultCode.Success;
}
public ResultCode GetUserExistenceImpl(ServiceCtx context)
{
ResultCode resultCode = CheckUserId(context, out UserId userId);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
context.ResponseData.Write(context.Device.System.AccountManager.TryGetUser(userId, out _));
return ResultCode.Success;
}
public ResultCode ListAllUsers(ServiceCtx context)
{
return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers());
}
public ResultCode ListOpenUsers(ServiceCtx context)
{
return WriteUserList(context, context.Device.System.AccountManager.GetOpenedUsers());
}
private ResultCode WriteUserList(ServiceCtx context, IEnumerable<UserProfile> profiles)
{
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.InvalidBuffer;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
ulong outputSize = context.Request.RecvListBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
ulong offset = 0;
foreach (UserProfile userProfile in profiles)
{
if (offset + 0x10 > outputSize)
{
break;
}
context.Memory.Write(outputPosition + offset, userProfile.UserId.High);
context.Memory.Write(outputPosition + offset + 8, userProfile.UserId.Low);
offset += 0x10;
}
return ResultCode.Success;
}
public ResultCode GetLastOpenedUser(ServiceCtx context)
{
context.Device.System.AccountManager.LastOpenedUser.UserId.Write(context.ResponseData);
return ResultCode.Success;
}
public ResultCode GetProfile(ServiceCtx context, out IProfile profile)
{
profile = default;
ResultCode resultCode = CheckUserId(context, out UserId userId);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile))
{
Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!");
return ResultCode.UserNotFound;
}
profile = new IProfile(userProfile);
// Doesn't occur in our case.
// return ResultCode.NullObject;
return ResultCode.Success;
}
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
{
context.ResponseData.Write(_serviceFlag != AccountServiceFlag.Application);
return ResultCode.Success;
}
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
{
if (context.Device.System.AccountManager.GetUserCount() < 1)
{
// Invalid UserId.
UserId.Null.Write(context.ResponseData);
return ResultCode.UserNotFound;
}
bool isNetworkServiceAccountRequired = context.RequestData.ReadBoolean();
if (isNetworkServiceAccountRequired)
{
// NOTE: This checks something related to baas (online), and then return an invalid UserId if the check in baas returns an error code.
// In our case, we can just log it for now.
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { isNetworkServiceAccountRequired });
}
// NOTE: As we returned an invalid UserId if there is more than one user earlier, now we can return only the first one.
context.Device.System.AccountManager.GetFirst().UserId.Write(context.ResponseData);
return ResultCode.Success;
}
public ResultCode CheckNetworkServiceAvailabilityAsync(ServiceCtx context, out IAsyncContext asyncContext)
{
KEvent asyncEvent = new(context.Device.System.KernelContext);
AsyncExecution asyncExecution = new(asyncEvent);
asyncExecution.Initialize(1000, CheckNetworkServiceAvailabilityAsyncImpl);
asyncContext = new IAsyncContext(asyncExecution);
// return ResultCode.NullObject if the IAsyncContext pointer is null. Doesn't occur in our case.
return ResultCode.Success;
}
private async Task CheckNetworkServiceAvailabilityAsyncImpl(CancellationToken token)
{
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
// TODO: Use a real function instead, with the CancellationToken.
await Task.CompletedTask;
}
public ResultCode StoreSaveDataThumbnail(ServiceCtx context)
{
ResultCode resultCode = CheckUserId(context, out UserId _);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (context.Request.SendBuff.Count == 0)
{
return ResultCode.InvalidBuffer;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
if (inputSize != 0x24000)
{
return ResultCode.InvalidBufferSize;
}
byte[] thumbnailBuffer = new byte[inputSize];
context.Memory.Read(inputPosition, thumbnailBuffer);
// NOTE: Account service call nn::fs::WriteSaveDataThumbnailFile().
// TODO: Store thumbnailBuffer somewhere, in save data 0x8000000000000010 ?
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
return ResultCode.Success;
}
public ResultCode ClearSaveDataThumbnail(ServiceCtx context)
{
ResultCode resultCode = CheckUserId(context, out UserId _);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
/*
// NOTE: Doesn't occur in our case.
if (userId == null)
{
return ResultCode.InvalidArgument;
}
*/
// NOTE: Account service call nn::fs::WriteSaveDataThumbnailFileHeader();
// TODO: Clear the Thumbnail somewhere, in save data 0x8000000000000010 ?
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
return ResultCode.Success;
}
public ResultCode ListOpenContextStoredUsers(ServiceCtx context)
{
return WriteUserList(context, context.Device.System.AccountManager.GetStoredOpenedUsers());
}
public ResultCode ListQualifiedUsers(ServiceCtx context)
{
// TODO: Determine how users are "qualified". We assume all users are "qualified" for now.
return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers());
}
public ResultCode CheckUserId(ServiceCtx context, out UserId userId)
{
userId = context.RequestData.ReadStruct<UserId>();
if (userId.IsNull)
{
return ResultCode.NullArgument;
}
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,56 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext
{
class AsyncExecution
{
private readonly CancellationTokenSource _tokenSource;
private readonly CancellationToken _token;
public KEvent SystemEvent { get; }
public bool IsInitialized { get; private set; }
public bool IsRunning { get; private set; }
public AsyncExecution(KEvent asyncEvent)
{
SystemEvent = asyncEvent;
_tokenSource = new CancellationTokenSource();
_token = _tokenSource.Token;
}
public void Initialize(int timeout, Func<CancellationToken, Task> taskAsync)
{
Task.Run(async () =>
{
IsRunning = true;
_tokenSource.CancelAfter(timeout);
try
{
await taskAsync(_token);
}
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.ServiceAcc, $"Exception: {ex.Message}");
}
SystemEvent.ReadableEvent.Signal();
IsRunning = false;
}, _token);
IsInitialized = true;
}
public void Cancel()
{
_tokenSource.Cancel();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -0,0 +1,129 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[Service("acc:su", AccountServiceFlag.Administrator)] // Max Sessions: 8
class IAccountServiceForAdministrator : IpcService
{
private ApplicationServiceServer _applicationServiceServer;
public IAccountServiceForAdministrator(ServiceCtx context, AccountServiceFlag serviceFlag)
{
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
}
[CommandCmif(0)]
// GetUserCount() -> i32
public ResultCode GetUserCount(ServiceCtx context)
{
return _applicationServiceServer.GetUserCountImpl(context);
}
[CommandCmif(1)]
// GetUserExistence(nn::account::Uid) -> bool
public ResultCode GetUserExistence(ServiceCtx context)
{
return _applicationServiceServer.GetUserExistenceImpl(context);
}
[CommandCmif(2)]
// ListAllUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListAllUsers(ServiceCtx context)
{
return _applicationServiceServer.ListAllUsers(context);
}
[CommandCmif(3)]
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListOpenUsers(ServiceCtx context)
{
return _applicationServiceServer.ListOpenUsers(context);
}
[CommandCmif(4)]
// GetLastOpenedUser() -> nn::account::Uid
public ResultCode GetLastOpenedUser(ServiceCtx context)
{
return _applicationServiceServer.GetLastOpenedUser(context);
}
[CommandCmif(5)]
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
public ResultCode GetProfile(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile);
if (resultCode == ResultCode.Success)
{
MakeObject(context, iProfile);
}
return resultCode;
}
[CommandCmif(50)]
// IsUserRegistrationRequestPermitted(pid) -> bool
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
{
// NOTE: pid is unused.
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
}
[CommandCmif(51)]
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
{
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
}
[CommandCmif(102)]
// GetBaasAccountManagerForSystemService(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
MakeObject(context, new IManagerForSystemService(userId));
// Doesn't occur in our case.
// return ResultCode.NullObject;
return ResultCode.Success;
}
[CommandCmif(140)] // 6.0.0+
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListQualifiedUsers(ServiceCtx context)
{
return _applicationServiceServer.ListQualifiedUsers(context);
}
[CommandCmif(205)]
// GetProfileEditor(nn::account::Uid) -> object<nn::account::profile::IProfileEditor>
public ResultCode GetProfileEditor(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile))
{
Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!");
return ResultCode.UserNotFound;
}
MakeObject(context, new IProfileEditor(userProfile));
// Doesn't occur in our case.
// return ResultCode.NullObject;
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,200 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService;
using Ryujinx.HLE.HOS.Services.Arp;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[Service("acc:u0", AccountServiceFlag.Application)] // Max Sessions: 4
class IAccountServiceForApplication : IpcService
{
private ApplicationServiceServer _applicationServiceServer;
public IAccountServiceForApplication(ServiceCtx context, AccountServiceFlag serviceFlag)
{
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
}
[CommandCmif(0)]
// GetUserCount() -> i32
public ResultCode GetUserCount(ServiceCtx context)
{
return _applicationServiceServer.GetUserCountImpl(context);
}
[CommandCmif(1)]
// GetUserExistence(nn::account::Uid) -> bool
public ResultCode GetUserExistence(ServiceCtx context)
{
return _applicationServiceServer.GetUserExistenceImpl(context);
}
[CommandCmif(2)]
// ListAllUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListAllUsers(ServiceCtx context)
{
return _applicationServiceServer.ListAllUsers(context);
}
[CommandCmif(3)]
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListOpenUsers(ServiceCtx context)
{
return _applicationServiceServer.ListOpenUsers(context);
}
[CommandCmif(4)]
// GetLastOpenedUser() -> nn::account::Uid
public ResultCode GetLastOpenedUser(ServiceCtx context)
{
return _applicationServiceServer.GetLastOpenedUser(context);
}
[CommandCmif(5)]
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
public ResultCode GetProfile(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile);
if (resultCode == ResultCode.Success)
{
MakeObject(context, iProfile);
}
return resultCode;
}
[CommandCmif(50)]
// IsUserRegistrationRequestPermitted(pid) -> bool
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
{
// NOTE: pid is unused.
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
}
[CommandCmif(51)]
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
{
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
}
[CommandCmif(100)]
[CommandCmif(140)] // 6.0.0+
[CommandCmif(160)] // 13.0.0+
// InitializeApplicationInfo(u64 pid_placeholder, pid)
public ResultCode InitializeApplicationInfo(ServiceCtx context)
{
// NOTE: In call 100, account service use the pid_placeholder instead of the real pid, which is wrong, call 140 fix that.
/*
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationLaunchProperty() with the current PID and store the result (ApplicationLaunchProperty) internally.
// For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented.
if (nn::arp::detail::IReader::GetApplicationLaunchProperty() == 0xCC9D) // ResultCode.InvalidProcessId
{
return ResultCode.InvalidArgument;
}
*/
// TODO: Determine where ApplicationLaunchProperty is used.
ApplicationLaunchProperty applicationLaunchProperty = ApplicationLaunchProperty.GetByPid(context);
Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { applicationLaunchProperty.TitleId });
return ResultCode.Success;
}
[CommandCmif(101)]
// GetBaasAccountManagerForApplication(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
MakeObject(context, new IManagerForApplication(userId));
// Doesn't occur in our case.
// return ResultCode.NullObject;
return ResultCode.Success;
}
[CommandCmif(103)] // 4.0.0+
// CheckNetworkServiceAvailabilityAsync() -> object<nn::account::detail::IAsyncContext>
public ResultCode CheckNetworkServiceAvailabilityAsync(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.CheckNetworkServiceAvailabilityAsync(context, out IAsyncContext asyncContext);
if (resultCode == ResultCode.Success)
{
MakeObject(context, asyncContext);
}
return resultCode;
}
[CommandCmif(110)]
// StoreSaveDataThumbnail(nn::account::Uid, buffer<bytes, 5>)
public ResultCode StoreSaveDataThumbnail(ServiceCtx context)
{
return _applicationServiceServer.StoreSaveDataThumbnail(context);
}
[CommandCmif(111)]
// ClearSaveDataThumbnail(nn::account::Uid)
public ResultCode ClearSaveDataThumbnail(ServiceCtx context)
{
return _applicationServiceServer.ClearSaveDataThumbnail(context);
}
[CommandCmif(130)] // 5.0.0+
// LoadOpenContext(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
public ResultCode LoadOpenContext(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId);
if (resultCode != ResultCode.Success)
{
return resultCode;
}
MakeObject(context, new IManagerForApplication(userId));
return ResultCode.Success;
}
[CommandCmif(60)] // 5.0.0-5.1.0
[CommandCmif(131)] // 6.0.0+
// ListOpenContextStoredUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListOpenContextStoredUsers(ServiceCtx context)
{
return _applicationServiceServer.ListOpenContextStoredUsers(context);
}
[CommandCmif(141)] // 6.0.0+
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListQualifiedUsers(ServiceCtx context)
{
return _applicationServiceServer.ListQualifiedUsers(context);
}
[CommandCmif(150)] // 6.0.0+
// IsUserAccountSwitchLocked() -> bool
public ResultCode IsUserAccountSwitchLocked(ServiceCtx context)
{
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
// But since we use LibHac and we load one Application at a time, it's not necessary.
context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,107 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[Service("acc:u1", AccountServiceFlag.SystemService)] // Max Sessions: 16
class IAccountServiceForSystemService : IpcService
{
private ApplicationServiceServer _applicationServiceServer;
public IAccountServiceForSystemService(ServiceCtx context, AccountServiceFlag serviceFlag)
{
_applicationServiceServer = new ApplicationServiceServer(serviceFlag);
}
[CommandCmif(0)]
// GetUserCount() -> i32
public ResultCode GetUserCount(ServiceCtx context)
{
return _applicationServiceServer.GetUserCountImpl(context);
}
[CommandCmif(1)]
// GetUserExistence(nn::account::Uid) -> bool
public ResultCode GetUserExistence(ServiceCtx context)
{
return _applicationServiceServer.GetUserExistenceImpl(context);
}
[CommandCmif(2)]
// ListAllUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListAllUsers(ServiceCtx context)
{
return _applicationServiceServer.ListAllUsers(context);
}
[CommandCmif(3)]
// ListOpenUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListOpenUsers(ServiceCtx context)
{
return _applicationServiceServer.ListOpenUsers(context);
}
[CommandCmif(4)]
// GetLastOpenedUser() -> nn::account::Uid
public ResultCode GetLastOpenedUser(ServiceCtx context)
{
return _applicationServiceServer.GetLastOpenedUser(context);
}
[CommandCmif(5)]
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
public ResultCode GetProfile(ServiceCtx context)
{
ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile);
if (resultCode == ResultCode.Success)
{
MakeObject(context, iProfile);
}
return resultCode;
}
[CommandCmif(50)]
// IsUserRegistrationRequestPermitted(pid) -> bool
public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context)
{
// NOTE: pid is unused.
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
}
[CommandCmif(51)]
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
{
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
}
[CommandCmif(102)]
// GetBaasAccountManagerForSystemService(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context)
{
UserId userId = context.RequestData.ReadStruct<UserId>();
if (userId.IsNull)
{
return ResultCode.NullArgument;
}
MakeObject(context, new IManagerForSystemService(userId));
// Doesn't occur in our case.
// return ResultCode.NullObject;
return ResultCode.Success;
}
[CommandCmif(140)] // 6.0.0+
// ListQualifiedUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListQualifiedUsers(ServiceCtx context)
{
return _applicationServiceServer.ListQualifiedUsers(context);
}
}
}

View file

@ -0,0 +1,79 @@
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class IAsyncContext : IpcService
{
protected AsyncExecution AsyncExecution;
public IAsyncContext(AsyncExecution asyncExecution)
{
AsyncExecution = asyncExecution;
}
[CommandCmif(0)]
// GetSystemEvent() -> handle<copy>
public ResultCode GetSystemEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(AsyncExecution.SystemEvent.ReadableEvent, out int _systemEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_systemEventHandle);
return ResultCode.Success;
}
[CommandCmif(1)]
// Cancel()
public ResultCode Cancel(ServiceCtx context)
{
if (!AsyncExecution.IsInitialized)
{
return ResultCode.AsyncExecutionNotInitialized;
}
if (AsyncExecution.IsRunning)
{
AsyncExecution.Cancel();
}
return ResultCode.Success;
}
[CommandCmif(2)]
// HasDone() -> b8
public ResultCode HasDone(ServiceCtx context)
{
if (!AsyncExecution.IsInitialized)
{
return ResultCode.AsyncExecutionNotInitialized;
}
context.ResponseData.Write(AsyncExecution.SystemEvent.ReadableEvent.IsSignaled());
return ResultCode.Success;
}
[CommandCmif(3)]
// GetResult()
public ResultCode GetResult(ServiceCtx context)
{
if (!AsyncExecution.IsInitialized)
{
return ResultCode.AsyncExecutionNotInitialized;
}
if (!AsyncExecution.SystemEvent.ReadableEvent.IsSignaled())
{
return ResultCode.Unknown41;
}
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,38 @@
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class IAsyncNetworkServiceLicenseKindContext : IAsyncContext
{
private NetworkServiceLicenseKind? _serviceLicenseKind;
public IAsyncNetworkServiceLicenseKindContext(AsyncExecution asyncExecution, NetworkServiceLicenseKind? serviceLicenseKind) : base(asyncExecution)
{
_serviceLicenseKind = serviceLicenseKind;
}
[CommandCmif(100)]
// GetNetworkServiceLicenseKind() -> nn::account::NetworkServiceLicenseKind
public ResultCode GetNetworkServiceLicenseKind(ServiceCtx context)
{
if (!AsyncExecution.IsInitialized)
{
return ResultCode.AsyncExecutionNotInitialized;
}
if (!AsyncExecution.SystemEvent.ReadableEvent.IsSignaled())
{
return ResultCode.Unknown41;
}
if (!_serviceLicenseKind.HasValue)
{
return ResultCode.MissingNetworkServiceLicenseKind;
}
context.ResponseData.Write((uint)_serviceLicenseKind.Value);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[Service("acc:aa", AccountServiceFlag.BaasAccessTokenAccessor)] // Max Sessions: 4
class IBaasAccessTokenAccessor : IpcService
{
public IBaasAccessTokenAccessor(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,11 @@
using Ryujinx.HLE.HOS.Services.Account.Acc.Types;
using System.Text.Json.Serialization;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ProfilesJson))]
internal partial class ProfilesJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
enum AccountServiceFlag
{
Administrator = 100,
SystemService = 101,
Application = 102,
BaasAccessTokenAccessor = 200
}
}

View file

@ -0,0 +1,12 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
public enum AccountState
{
Closed,
Open
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
enum NetworkServiceLicenseKind : uint
{
NoSubscription,
Subscribed
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types
{
internal struct ProfilesJson
{
public List<UserProfileJson> Profiles { get; set; }
public string LastOpened { get; set; }
}
}

View file

@ -0,0 +1,64 @@
using LibHac.Account;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
[StructLayout(LayoutKind.Sequential)]
public readonly record struct UserId
{
public readonly long High;
public readonly long Low;
public bool IsNull => (Low | High) == 0;
public static UserId Null => new UserId(0, 0);
public UserId(long low, long high)
{
Low = low;
High = high;
}
public UserId(byte[] bytes)
{
High = BitConverter.ToInt64(bytes, 0);
Low = BitConverter.ToInt64(bytes, 8);
}
public UserId(string hex)
{
if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains))
{
throw new ArgumentException("Invalid Hex value!", nameof(hex));
}
Low = long.Parse(hex.AsSpan(16), NumberStyles.HexNumber);
High = long.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber);
}
public void Write(BinaryWriter binaryWriter)
{
binaryWriter.Write(High);
binaryWriter.Write(Low);
}
public override string ToString()
{
return High.ToString("x16") + Low.ToString("x16");
}
public Uid ToLibHacUid()
{
return new Uid((ulong)High, (ulong)Low);
}
public UInt128 ToUInt128()
{
return new UInt128((ulong)High, (ulong)Low);
}
}
}

View file

@ -0,0 +1,87 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
public class UserProfile
{
public UserId UserId { get; }
public long LastModifiedTimestamp { get; set; }
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
UpdateLastModifiedTimestamp();
}
}
private byte[] _image;
public byte[] Image
{
get => _image;
set
{
_image = value;
UpdateLastModifiedTimestamp();
}
}
private AccountState _accountState;
public AccountState AccountState
{
get => _accountState;
set
{
_accountState = value;
UpdateLastModifiedTimestamp();
}
}
public AccountState _onlinePlayState;
public AccountState OnlinePlayState
{
get => _onlinePlayState;
set
{
_onlinePlayState = value;
UpdateLastModifiedTimestamp();
}
}
public UserProfile(UserId userId, string name, byte[] image, long lastModifiedTimestamp = 0)
{
UserId = userId;
Name = name;
Image = image;
AccountState = AccountState.Closed;
OnlinePlayState = AccountState.Closed;
if (lastModifiedTimestamp != 0)
{
LastModifiedTimestamp = lastModifiedTimestamp;
}
else
{
UpdateLastModifiedTimestamp();
}
}
private void UpdateLastModifiedTimestamp()
{
LastModifiedTimestamp = (long)(DateTime.Now - DateTime.UnixEpoch).TotalSeconds;
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types
{
internal struct UserProfileJson
{
public string UserId { get; set; }
public string Name { get; set; }
public AccountState AccountState { get; set; }
public AccountState OnlinePlayState { get; set; }
public long LastModifiedTimestamp { get; set; }
public byte[] Image { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Account.Dauth
{
[Service("dauth:0")] // 5.0.0+
class IService : IpcService
{
public IService(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,24 @@
namespace Ryujinx.HLE.HOS.Services.Account
{
enum ResultCode
{
ModuleId = 124,
ErrorCodeShift = 9,
Success = 0,
NullArgument = (20 << ErrorCodeShift) | ModuleId,
InvalidArgument = (22 << ErrorCodeShift) | ModuleId,
NullInputBuffer = (30 << ErrorCodeShift) | ModuleId,
InvalidBufferSize = (31 << ErrorCodeShift) | ModuleId,
InvalidBuffer = (32 << ErrorCodeShift) | ModuleId,
AsyncExecutionNotInitialized = (40 << ErrorCodeShift) | ModuleId,
Unknown41 = (41 << ErrorCodeShift) | ModuleId,
InternetRequestDenied = (59 << ErrorCodeShift) | ModuleId,
UserNotFound = (100 << ErrorCodeShift) | ModuleId,
NullObject = (302 << ErrorCodeShift) | ModuleId,
Unknown341 = (341 << ErrorCodeShift) | ModuleId,
MissingNetworkServiceLicenseKind = (400 << ErrorCodeShift) | ModuleId,
InvalidIdTokenCacheBufferSize = (451 << ErrorCodeShift) | ModuleId
}
}

View file

@ -0,0 +1,105 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
{
class ILibraryAppletProxy : IpcService
{
private readonly ulong _pid;
public ILibraryAppletProxy(ulong pid)
{
_pid = pid;
}
[CommandCmif(0)]
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
public ResultCode GetCommonStateGetter(ServiceCtx context)
{
MakeObject(context, new ICommonStateGetter(context));
return ResultCode.Success;
}
[CommandCmif(1)]
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
[CommandCmif(2)]
// GetWindowController() -> object<nn::am::service::IWindowController>
public ResultCode GetWindowController(ServiceCtx context)
{
MakeObject(context, new IWindowController(_pid));
return ResultCode.Success;
}
[CommandCmif(3)]
// GetAudioController() -> object<nn::am::service::IAudioController>
public ResultCode GetAudioController(ServiceCtx context)
{
MakeObject(context, new IAudioController());
return ResultCode.Success;
}
[CommandCmif(4)]
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}
[CommandCmif(10)]
// GetProcessWindingController() -> object<nn::am::service::IProcessWindingController>
public ResultCode GetProcessWindingController(ServiceCtx context)
{
MakeObject(context, new IProcessWindingController());
return ResultCode.Success;
}
[CommandCmif(11)]
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletCreator());
return ResultCode.Success;
}
[CommandCmif(20)]
// OpenLibraryAppletSelfAccessor() -> object<nn::am::service::ILibraryAppletSelfAccessor>
public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletSelfAccessor(context));
return ResultCode.Success;
}
[CommandCmif(21)]
// GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions>
public ResultCode GetAppletCommonFunctions(ServiceCtx context)
{
MakeObject(context, new IAppletCommonFunctions());
return ResultCode.Success;
}
[CommandCmif(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
public ResultCode GetDebugFunctions(ServiceCtx context)
{
MakeObject(context, new IDebugFunctions());
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,104 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
{
class ISystemAppletProxy : IpcService
{
private readonly ulong _pid;
public ISystemAppletProxy(ulong pid)
{
_pid = pid;
}
[CommandCmif(0)]
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
public ResultCode GetCommonStateGetter(ServiceCtx context)
{
MakeObject(context, new ICommonStateGetter(context));
return ResultCode.Success;
}
[CommandCmif(1)]
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
[CommandCmif(2)]
// GetWindowController() -> object<nn::am::service::IWindowController>
public ResultCode GetWindowController(ServiceCtx context)
{
MakeObject(context, new IWindowController(_pid));
return ResultCode.Success;
}
[CommandCmif(3)]
// GetAudioController() -> object<nn::am::service::IAudioController>
public ResultCode GetAudioController(ServiceCtx context)
{
MakeObject(context, new IAudioController());
return ResultCode.Success;
}
[CommandCmif(4)]
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}
[CommandCmif(11)]
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletCreator());
return ResultCode.Success;
}
[CommandCmif(20)]
// GetHomeMenuFunctions() -> object<nn::am::service::IHomeMenuFunctions>
public ResultCode GetHomeMenuFunctions(ServiceCtx context)
{
MakeObject(context, new IHomeMenuFunctions(context.Device.System));
return ResultCode.Success;
}
[CommandCmif(21)]
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
public ResultCode GetGlobalStateController(ServiceCtx context)
{
MakeObject(context, new IGlobalStateController());
return ResultCode.Success;
}
[CommandCmif(22)]
// GetApplicationCreator() -> object<nn::am::service::IApplicationCreator>
public ResultCode GetApplicationCreator(ServiceCtx context)
{
MakeObject(context, new IApplicationCreator());
return ResultCode.Success;
}
[CommandCmif(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
public ResultCode GetDebugFunctions(ServiceCtx context)
{
MakeObject(context, new IDebugFunctions());
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,261 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator
{
class ILibraryAppletAccessor : DisposableIpcService
{
private KernelContext _kernelContext;
private IApplet _applet;
private AppletSession _normalSession;
private AppletSession _interactiveSession;
private KEvent _stateChangedEvent;
private KEvent _normalOutDataEvent;
private KEvent _interactiveOutDataEvent;
private int _stateChangedEventHandle;
private int _normalOutDataEventHandle;
private int _interactiveOutDataEventHandle;
private int _indirectLayerHandle;
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
{
_kernelContext = system.KernelContext;
_stateChangedEvent = new KEvent(system.KernelContext);
_normalOutDataEvent = new KEvent(system.KernelContext);
_interactiveOutDataEvent = new KEvent(system.KernelContext);
_applet = AppletManager.Create(appletId, system);
_normalSession = new AppletSession();
_interactiveSession = new AppletSession();
_applet.AppletStateChanged += OnAppletStateChanged;
_normalSession.DataAvailable += OnNormalOutData;
_interactiveSession.DataAvailable += OnInteractiveOutData;
Logger.Info?.Print(LogClass.ServiceAm, $"Applet '{appletId}' created.");
}
private void OnAppletStateChanged(object sender, EventArgs e)
{
_stateChangedEvent.WritableEvent.Signal();
}
private void OnNormalOutData(object sender, EventArgs e)
{
_normalOutDataEvent.WritableEvent.Signal();
}
private void OnInteractiveOutData(object sender, EventArgs e)
{
_interactiveOutDataEvent.WritableEvent.Signal();
}
[CommandCmif(0)]
// GetAppletStateChangedEvent() -> handle<copy>
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
{
if (_stateChangedEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out _stateChangedEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangedEventHandle);
return ResultCode.Success;
}
[CommandCmif(10)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return (ResultCode)_applet.Start(_normalSession.GetConsumer(), _interactiveSession.GetConsumer());
}
[CommandCmif(20)]
// RequestExit()
public ResultCode RequestExit(ServiceCtx context)
{
// TODO: Since we don't support software Applet for now, we can just signals the changed state of the applet.
_stateChangedEvent.ReadableEvent.Signal();
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(30)]
// GetResult()
public ResultCode GetResult(ServiceCtx context)
{
return (ResultCode)_applet.GetResult();
}
[CommandCmif(60)]
// PresetLibraryAppletGpuTimeSliceZero()
public ResultCode PresetLibraryAppletGpuTimeSliceZero(ServiceCtx context)
{
// NOTE: This call reset two internal fields to 0 and one internal field to "true".
// It seems to be used only with software keyboard inline.
// Since we doesn't support applets for now, it's fine to stub it.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(100)]
// PushInData(object<nn::am::service::IStorage>)
public ResultCode PushInData(ServiceCtx context)
{
IStorage data = GetObject<IStorage>(context, 0);
_normalSession.Push(data.Data);
return ResultCode.Success;
}
[CommandCmif(101)]
// PopOutData() -> object<nn::am::service::IStorage>
public ResultCode PopOutData(ServiceCtx context)
{
if (_normalSession.TryPop(out byte[] data))
{
MakeObject(context, new IStorage(data));
_normalOutDataEvent.WritableEvent.Clear();
return ResultCode.Success;
}
return ResultCode.NotAvailable;
}
[CommandCmif(103)]
// PushInteractiveInData(object<nn::am::service::IStorage>)
public ResultCode PushInteractiveInData(ServiceCtx context)
{
IStorage data = GetObject<IStorage>(context, 0);
_interactiveSession.Push(data.Data);
return ResultCode.Success;
}
[CommandCmif(104)]
// PopInteractiveOutData() -> object<nn::am::service::IStorage>
public ResultCode PopInteractiveOutData(ServiceCtx context)
{
if (_interactiveSession.TryPop(out byte[] data))
{
MakeObject(context, new IStorage(data));
_interactiveOutDataEvent.WritableEvent.Clear();
return ResultCode.Success;
}
return ResultCode.NotAvailable;
}
[CommandCmif(105)]
// GetPopOutDataEvent() -> handle<copy>
public ResultCode GetPopOutDataEvent(ServiceCtx context)
{
if (_normalOutDataEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out _normalOutDataEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_normalOutDataEventHandle);
return ResultCode.Success;
}
[CommandCmif(106)]
// GetPopInteractiveOutDataEvent() -> handle<copy>
public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context)
{
if (_interactiveOutDataEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out _interactiveOutDataEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_interactiveOutDataEventHandle);
return ResultCode.Success;
}
[CommandCmif(110)]
// NeedsToExitProcess()
public ResultCode NeedsToExitProcess(ServiceCtx context)
{
return ResultCode.Stubbed;
}
[CommandCmif(150)]
// RequestForAppletToGetForeground()
public ResultCode RequestForAppletToGetForeground(ServiceCtx context)
{
return ResultCode.Stubbed;
}
[CommandCmif(160)] // 2.0.0+
// GetIndirectLayerConsumerHandle() -> u64 indirect_layer_consumer_handle
public ResultCode GetIndirectLayerConsumerHandle(ServiceCtx context)
{
Horizon horizon = _kernelContext.Device.System;
_indirectLayerHandle = horizon.AppletState.IndirectLayerHandles.Add(_applet);
context.ResponseData.Write((ulong)_indirectLayerHandle);
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (_stateChangedEventHandle != 0)
{
_kernelContext.Syscall.CloseHandle(_stateChangedEventHandle);
}
if (_normalOutDataEventHandle != 0)
{
_kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle);
}
if (_interactiveOutDataEventHandle != 0)
{
_kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle);
}
}
Horizon horizon = _kernelContext.Device.System;
horizon.AppletState.IndirectLayerHandles.Delete(_indirectLayerHandle);
}
}
}

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class AppletStandalone
{
public AppletId AppletId;
public LibraryAppletMode LibraryAppletMode;
public Queue<byte[]> InputData;
public AppletStandalone()
{
InputData = new Queue<byte[]>();
}
}
}

View file

@ -0,0 +1,78 @@
using Ryujinx.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class ILibraryAppletSelfAccessor : IpcService
{
private AppletStandalone _appletStandalone = new AppletStandalone();
public ILibraryAppletSelfAccessor(ServiceCtx context)
{
if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
{
// Create MiiEdit data.
_appletStandalone = new AppletStandalone()
{
AppletId = AppletId.MiiEdit,
LibraryAppletMode = LibraryAppletMode.AllForeground
};
byte[] miiEditInputData = new byte[0x100];
miiEditInputData[0] = 0x03; // Hardcoded unknown value.
_appletStandalone.InputData.Enqueue(miiEditInputData);
}
else
{
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
}
}
[CommandCmif(0)]
// PopInData() -> object<nn::am::service::IStorage>
public ResultCode PopInData(ServiceCtx context)
{
byte[] appletData = _appletStandalone.InputData.Dequeue();
if (appletData.Length == 0)
{
return ResultCode.NotAvailable;
}
MakeObject(context, new IStorage(appletData));
return ResultCode.Success;
}
[CommandCmif(11)]
// GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo
public ResultCode GetLibraryAppletInfo(ServiceCtx context)
{
LibraryAppletInfo libraryAppletInfo = new LibraryAppletInfo()
{
AppletId = _appletStandalone.AppletId,
LibraryAppletMode = _appletStandalone.LibraryAppletMode
};
context.ResponseData.WriteStruct(libraryAppletInfo);
return ResultCode.Success;
}
[CommandCmif(14)]
// GetCallerAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo
public ResultCode GetCallerAppletIdentityInfo(ServiceCtx context)
{
AppletIdentifyInfo appletIdentifyInfo = new AppletIdentifyInfo()
{
AppletId = AppletId.QLaunch,
TitleId = 0x0100000000001000
};
context.ResponseData.WriteStruct(appletIdentifyInfo);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,24 @@
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy
{
class IProcessWindingController : IpcService
{
public IProcessWindingController() { }
[CommandCmif(0)]
// GetLaunchReason() -> nn::am::service::AppletProcessLaunchReason
public ResultCode GetLaunchReason(ServiceCtx context)
{
// NOTE: Flag is set by using an internal field.
AppletProcessLaunchReason appletProcessLaunchReason = new AppletProcessLaunchReason()
{
Flag = 0
};
context.ResponseData.WriteStruct(appletProcessLaunchReason);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IAppletCommonFunctions : IpcService
{
public IAppletCommonFunctions() { }
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IApplicationCreator : IpcService
{
public IApplicationCreator() { }
}
}

View file

@ -0,0 +1,66 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IAudioController : IpcService
{
public IAudioController() { }
[CommandCmif(0)]
// SetExpectedMasterVolume(f32, f32)
public ResultCode SetExpectedMasterVolume(ServiceCtx context)
{
float appletVolume = context.RequestData.ReadSingle();
float libraryAppletVolume = context.RequestData.ReadSingle();
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(1)]
// GetMainAppletExpectedMasterVolume() -> f32
public ResultCode GetMainAppletExpectedMasterVolume(ServiceCtx context)
{
context.ResponseData.Write(1f);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(2)]
// GetLibraryAppletExpectedMasterVolume() -> f32
public ResultCode GetLibraryAppletExpectedMasterVolume(ServiceCtx context)
{
context.ResponseData.Write(1f);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(3)]
// ChangeMainAppletMasterVolume(f32, u64)
public ResultCode ChangeMainAppletMasterVolume(ServiceCtx context)
{
float unknown0 = context.RequestData.ReadSingle();
long unknown1 = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(4)]
// SetTransparentVolumeRate(f32)
public ResultCode SetTransparentVolumeRate(ServiceCtx context)
{
float unknown0 = context.RequestData.ReadSingle();
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,285 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Settings.Types;
using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class ICommonStateGetter : IpcService
{
private Apm.ManagerServer _apmManagerServer;
private Apm.SystemManagerServer _apmSystemManagerServer;
private Lbl.LblControllerServer _lblControllerServer;
private bool _vrModeEnabled;
#pragma warning disable CS0414
private bool _lcdBacklighOffEnabled;
private bool _requestExitToLibraryAppletAtExecuteNextProgramEnabled;
#pragma warning restore CS0414
private int _messageEventHandle;
private int _displayResolutionChangedEventHandle;
public ICommonStateGetter(ServiceCtx context)
{
_apmManagerServer = new Apm.ManagerServer(context);
_apmSystemManagerServer = new Apm.SystemManagerServer(context);
_lblControllerServer = new Lbl.LblControllerServer(context);
}
[CommandCmif(0)]
// GetEventHandle() -> handle<copy>
public ResultCode GetEventHandle(ServiceCtx context)
{
KEvent messageEvent = context.Device.System.AppletState.MessageEvent;
if (_messageEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(messageEvent.ReadableEvent, out _messageEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_messageEventHandle);
return ResultCode.Success;
}
[CommandCmif(1)]
// ReceiveMessage() -> nn::am::AppletMessage
public ResultCode ReceiveMessage(ServiceCtx context)
{
if (!context.Device.System.AppletState.Messages.TryDequeue(out AppletMessage message))
{
return ResultCode.NoMessages;
}
KEvent messageEvent = context.Device.System.AppletState.MessageEvent;
// NOTE: Service checks if current states are different than the stored ones.
// Since we don't support any states for now, it's fine to check if there is still messages available.
if (context.Device.System.AppletState.Messages.IsEmpty)
{
messageEvent.ReadableEvent.Clear();
}
else
{
messageEvent.ReadableEvent.Signal();
}
context.ResponseData.Write((int)message);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetOperationMode() -> u8
public ResultCode GetOperationMode(ServiceCtx context)
{
OperationMode mode = context.Device.System.State.DockedMode
? OperationMode.Docked
: OperationMode.Handheld;
context.ResponseData.Write((byte)mode);
return ResultCode.Success;
}
[CommandCmif(6)]
// GetPerformanceMode() -> nn::apm::PerformanceMode
public ResultCode GetPerformanceMode(ServiceCtx context)
{
return (ResultCode)_apmManagerServer.GetPerformanceMode(context);
}
[CommandCmif(8)]
// GetBootMode() -> u8
public ResultCode GetBootMode(ServiceCtx context)
{
context.ResponseData.Write((byte)0); //Unknown value.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(9)]
// GetCurrentFocusState() -> u8
public ResultCode GetCurrentFocusState(ServiceCtx context)
{
context.ResponseData.Write((byte)context.Device.System.AppletState.FocusState);
return ResultCode.Success;
}
[CommandCmif(50)] // 3.0.0+
// IsVrModeEnabled() -> b8
public ResultCode IsVrModeEnabled(ServiceCtx context)
{
context.ResponseData.Write(_vrModeEnabled);
return ResultCode.Success;
}
[CommandCmif(51)] // 3.0.0+
// SetVrModeEnabled(b8)
public ResultCode SetVrModeEnabled(ServiceCtx context)
{
bool vrModeEnabled = context.RequestData.ReadBoolean();
UpdateVrMode(vrModeEnabled);
return ResultCode.Success;
}
[CommandCmif(52)] // 4.0.0+
// SetLcdBacklighOffEnabled(b8)
public ResultCode SetLcdBacklighOffEnabled(ServiceCtx context)
{
// NOTE: Service sets a private field here, maybe this field is used somewhere else to turned off the backlight.
// Since we don't support backlight, it's fine to do nothing.
_lcdBacklighOffEnabled = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(53)] // 7.0.0+
// BeginVrModeEx()
public ResultCode BeginVrModeEx(ServiceCtx context)
{
UpdateVrMode(true);
return ResultCode.Success;
}
[CommandCmif(54)] // 7.0.0+
// EndVrModeEx()
public ResultCode EndVrModeEx(ServiceCtx context)
{
UpdateVrMode(false);
return ResultCode.Success;
}
private void UpdateVrMode(bool vrModeEnabled)
{
if (_vrModeEnabled == vrModeEnabled)
{
return;
}
_vrModeEnabled = vrModeEnabled;
if (vrModeEnabled)
{
_lblControllerServer.EnableVrMode();
}
else
{
_lblControllerServer.DisableVrMode();
}
// TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used.
}
[CommandCmif(60)] // 3.0.0+
// GetDefaultDisplayResolution() -> (u32, u32)
public ResultCode GetDefaultDisplayResolution(ServiceCtx context)
{
// NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolution of omm service.
// IOperationModeManager::GetDefaultDisplayResolution of omm service call IManagerDisplayService::GetDisplayResolution of vi service.
(ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context);
context.ResponseData.Write((uint)width);
context.ResponseData.Write((uint)height);
return ResultCode.Success;
}
[CommandCmif(61)] // 3.0.0+
// GetDefaultDisplayResolutionChangeEvent() -> handle<copy>
public ResultCode GetDefaultDisplayResolutionChangeEvent(ServiceCtx context)
{
// NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolutionChangeEvent of omm service.
if (_displayResolutionChangedEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(context.Device.System.DisplayResolutionChangeEvent.ReadableEvent, out _displayResolutionChangedEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_displayResolutionChangedEventHandle);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(62)] // 4.0.0+
// GetHdcpAuthenticationState() -> s32 state
public ResultCode GetHdcpAuthenticationState(ServiceCtx context)
{
context.ResponseData.Write(0);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(66)] // 6.0.0+
// SetCpuBoostMode(u32 cpu_boost_mode)
public ResultCode SetCpuBoostMode(ServiceCtx context)
{
uint cpuBoostMode = context.RequestData.ReadUInt32();
if (cpuBoostMode > 1)
{
return ResultCode.InvalidParameters;
}
_apmSystemManagerServer.SetCpuBoostMode((Apm.CpuBoostMode)cpuBoostMode);
// TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used.
return ResultCode.Success;
}
[CommandCmif(91)] // 7.0.0+
// GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration
public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context)
{
return (ResultCode)_apmSystemManagerServer.GetCurrentPerformanceConfiguration(context);
}
[CommandCmif(300)] // 9.0.0+
// GetSettingsPlatformRegion() -> u8
public ResultCode GetSettingsPlatformRegion(ServiceCtx context)
{
PlatformRegion platformRegion = context.Device.System.State.DesiredRegionCode == (uint)RegionCode.China ? PlatformRegion.China : PlatformRegion.Global;
// FIXME: Call set:sys GetPlatformRegion
context.ResponseData.Write((byte)platformRegion);
return ResultCode.Success;
}
[CommandCmif(900)] // 11.0.0+
// SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled()
public ResultCode SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(ServiceCtx context)
{
// TODO : Find where the field is used.
_requestExitToLibraryAppletAtExecuteNextProgramEnabled = true;
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IDebugFunctions : IpcService
{
public IDebugFunctions() { }
}
}

View file

@ -0,0 +1,106 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IDisplayController : IpcService
{
private KTransferMemory _transferMem;
private bool _lastApplicationCaptureBufferAcquired;
private bool _callerAppletCaptureBufferAcquired;
public IDisplayController(ServiceCtx context)
{
_transferMem = context.Device.System.AppletCaptureBufferTransfer;
}
[CommandCmif(8)] // 2.0.0+
// TakeScreenShotOfOwnLayer(b8, s32)
public ResultCode TakeScreenShotOfOwnLayer(ServiceCtx context)
{
bool unknown1 = context.RequestData.ReadBoolean();
int unknown2 = context.RequestData.ReadInt32();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknown1, unknown2 });
return ResultCode.Success;
}
[CommandCmif(11)]
// ReleaseLastApplicationCaptureBuffer()
public ResultCode ReleaseLastApplicationCaptureBuffer(ServiceCtx context)
{
if (!_lastApplicationCaptureBufferAcquired)
{
return ResultCode.BufferNotAcquired;
}
_lastApplicationCaptureBufferAcquired = false;
return ResultCode.Success;
}
[CommandCmif(15)]
// ReleaseCallerAppletCaptureBuffer()
public ResultCode ReleaseCallerAppletCaptureBuffer(ServiceCtx context)
{
if (!_callerAppletCaptureBufferAcquired)
{
return ResultCode.BufferNotAcquired;
}
_callerAppletCaptureBufferAcquired = false;
return ResultCode.Success;
}
[CommandCmif(16)]
// AcquireLastApplicationCaptureBufferEx() -> (b8, handle<copy>)
public ResultCode AcquireLastApplicationCaptureBufferEx(ServiceCtx context)
{
if (_lastApplicationCaptureBufferAcquired)
{
return ResultCode.BufferAlreadyAcquired;
}
if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
_lastApplicationCaptureBufferAcquired = true;
context.ResponseData.Write(_lastApplicationCaptureBufferAcquired);
return ResultCode.Success;
}
[CommandCmif(18)]
// AcquireCallerAppletCaptureBufferEx() -> (b8, handle<copy>)
public ResultCode AcquireCallerAppletCaptureBufferEx(ServiceCtx context)
{
if (_callerAppletCaptureBufferAcquired)
{
return ResultCode.BufferAlreadyAcquired;
}
if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
_callerAppletCaptureBufferAcquired = true;
context.ResponseData.Write(_callerAppletCaptureBufferAcquired);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IGlobalStateController : IpcService
{
public IGlobalStateController() { }
}
}

View file

@ -0,0 +1,48 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IHomeMenuFunctions : IpcService
{
private KEvent _channelEvent;
private int _channelEventHandle;
public IHomeMenuFunctions(Horizon system)
{
// TODO: Signal this Event somewhere in future.
_channelEvent = new KEvent(system.KernelContext);
}
[CommandCmif(10)]
// RequestToGetForeground()
public ResultCode RequestToGetForeground(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(21)]
// GetPopFromGeneralChannelEvent() -> handle<copy>
public ResultCode GetPopFromGeneralChannelEvent(ServiceCtx context)
{
if (_channelEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_channelEvent.ReadableEvent, out _channelEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_channelEventHandle);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,91 @@
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class ILibraryAppletCreator : IpcService
{
public ILibraryAppletCreator() { }
[CommandCmif(0)]
// CreateLibraryApplet(u32, u32) -> object<nn::am::service::ILibraryAppletAccessor>
public ResultCode CreateLibraryApplet(ServiceCtx context)
{
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
int libraryAppletMode = context.RequestData.ReadInt32();
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
return ResultCode.Success;
}
[CommandCmif(10)]
// CreateStorage(u64) -> object<nn::am::service::IStorage>
public ResultCode CreateStorage(ServiceCtx context)
{
long size = context.RequestData.ReadInt64();
if (size <= 0)
{
return ResultCode.ObjectInvalid;
}
MakeObject(context, new IStorage(new byte[size]));
// NOTE: Returns ResultCode.MemoryAllocationFailed if IStorage is null, it doesn't occur in our case.
return ResultCode.Success;
}
[CommandCmif(11)]
// CreateTransferMemoryStorage(b8, u64, handle<copy>) -> object<nn::am::service::IStorage>
public ResultCode CreateTransferMemoryStorage(ServiceCtx context)
{
bool isReadOnly = (context.RequestData.ReadInt64() & 1) == 0;
long size = context.RequestData.ReadInt64();
int handle = context.Request.HandleDesc.ToCopy[0];
KTransferMemory transferMem = context.Process.HandleTable.GetObject<KTransferMemory>(handle);
if (size <= 0)
{
return ResultCode.ObjectInvalid;
}
byte[] data = new byte[transferMem.Size];
transferMem.Creator.CpuMemory.Read(transferMem.Address, data);
context.Device.System.KernelContext.Syscall.CloseHandle(handle);
MakeObject(context, new IStorage(data, isReadOnly));
return ResultCode.Success;
}
[CommandCmif(12)] // 2.0.0+
// CreateHandleStorage(u64, handle<copy>) -> object<nn::am::service::IStorage>
public ResultCode CreateHandleStorage(ServiceCtx context)
{
long size = context.RequestData.ReadInt64();
int handle = context.Request.HandleDesc.ToCopy[0];
KTransferMemory transferMem = context.Process.HandleTable.GetObject<KTransferMemory>(handle);
if (size <= 0)
{
return ResultCode.ObjectInvalid;
}
byte[] data = new byte[transferMem.Size];
transferMem.Creator.CpuMemory.Read(transferMem.Address, data);
context.Device.System.KernelContext.Syscall.CloseHandle(handle);
MakeObject(context, new IStorage(data));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,432 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types;
using Ryujinx.Horizon.Common;
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class ISelfController : IpcService
{
private readonly ulong _pid;
private KEvent _libraryAppletLaunchableEvent;
private int _libraryAppletLaunchableEventHandle;
private KEvent _accumulatedSuspendedTickChangedEvent;
private int _accumulatedSuspendedTickChangedEventHandle;
private object _fatalSectionLock = new object();
private int _fatalSectionCount;
// TODO: Set this when the game goes in suspension (go back to home menu ect), we currently don't support that so we can keep it set to 0.
private ulong _accumulatedSuspendedTickValue = 0;
// TODO: Determine where those fields are used.
private bool _screenShotPermission = false;
private bool _operationModeChangedNotification = false;
private bool _performanceModeChangedNotification = false;
private bool _restartMessageEnabled = false;
private bool _outOfFocusSuspendingEnabled = false;
private bool _handlesRequestToDisplay = false;
private bool _autoSleepDisabled = false;
private bool _albumImageTakenNotificationEnabled = false;
private bool _recordVolumeMuted = false;
private uint _screenShotImageOrientation = 0;
private uint _idleTimeDetectionExtension = 0;
public ISelfController(ServiceCtx context, ulong pid)
{
_libraryAppletLaunchableEvent = new KEvent(context.Device.System.KernelContext);
_pid = pid;
}
[CommandCmif(0)]
// Exit()
public ResultCode Exit(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(1)]
// LockExit()
public ResultCode LockExit(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(2)]
// UnlockExit()
public ResultCode UnlockExit(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(3)] // 2.0.0+
// EnterFatalSection()
public ResultCode EnterFatalSection(ServiceCtx context)
{
lock (_fatalSectionLock)
{
_fatalSectionCount++;
}
return ResultCode.Success;
}
[CommandCmif(4)] // 2.0.0+
// LeaveFatalSection()
public ResultCode LeaveFatalSection(ServiceCtx context)
{
ResultCode result = ResultCode.Success;
lock (_fatalSectionLock)
{
if (_fatalSectionCount != 0)
{
_fatalSectionCount--;
}
else
{
result = ResultCode.UnbalancedFatalSection;
}
}
return result;
}
[CommandCmif(9)]
// GetLibraryAppletLaunchableEvent() -> handle<copy>
public ResultCode GetLibraryAppletLaunchableEvent(ServiceCtx context)
{
_libraryAppletLaunchableEvent.ReadableEvent.Signal();
if (_libraryAppletLaunchableEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_libraryAppletLaunchableEvent.ReadableEvent, out _libraryAppletLaunchableEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_libraryAppletLaunchableEventHandle);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(10)]
// SetScreenShotPermission(u32)
public ResultCode SetScreenShotPermission(ServiceCtx context)
{
bool screenShotPermission = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotPermission });
_screenShotPermission = screenShotPermission;
return ResultCode.Success;
}
[CommandCmif(11)]
// SetOperationModeChangedNotification(b8)
public ResultCode SetOperationModeChangedNotification(ServiceCtx context)
{
bool operationModeChangedNotification = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { operationModeChangedNotification });
_operationModeChangedNotification = operationModeChangedNotification;
return ResultCode.Success;
}
[CommandCmif(12)]
// SetPerformanceModeChangedNotification(b8)
public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context)
{
bool performanceModeChangedNotification = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { performanceModeChangedNotification });
_performanceModeChangedNotification = performanceModeChangedNotification;
return ResultCode.Success;
}
[CommandCmif(13)]
// SetFocusHandlingMode(b8, b8, b8)
public ResultCode SetFocusHandlingMode(ServiceCtx context)
{
bool unknownFlag1 = context.RequestData.ReadBoolean();
bool unknownFlag2 = context.RequestData.ReadBoolean();
bool unknownFlag3 = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknownFlag1, unknownFlag2, unknownFlag3 });
return ResultCode.Success;
}
[CommandCmif(14)]
// SetRestartMessageEnabled(b8)
public ResultCode SetRestartMessageEnabled(ServiceCtx context)
{
bool restartMessageEnabled = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { restartMessageEnabled });
_restartMessageEnabled = restartMessageEnabled;
return ResultCode.Success;
}
[CommandCmif(16)] // 2.0.0+
// SetOutOfFocusSuspendingEnabled(b8)
public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context)
{
bool outOfFocusSuspendingEnabled = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { outOfFocusSuspendingEnabled });
_outOfFocusSuspendingEnabled = outOfFocusSuspendingEnabled;
return ResultCode.Success;
}
[CommandCmif(19)] // 3.0.0+
// SetScreenShotImageOrientation(u32)
public ResultCode SetScreenShotImageOrientation(ServiceCtx context)
{
uint screenShotImageOrientation = context.RequestData.ReadUInt32();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotImageOrientation });
_screenShotImageOrientation = screenShotImageOrientation;
return ResultCode.Success;
}
[CommandCmif(40)]
// CreateManagedDisplayLayer() -> u64
public ResultCode CreateManagedDisplayLayer(ServiceCtx context)
{
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid);
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
context.ResponseData.Write(layerId);
return ResultCode.Success;
}
[CommandCmif(41)] // 4.0.0+
// IsSystemBufferSharingEnabled()
public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context)
{
// NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled.
return ResultCode.NotImplemented;
}
[CommandCmif(44)] // 10.0.0+
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)
{
context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid);
context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid);
context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
context.ResponseData.Write(displayLayerId);
context.ResponseData.Write(recordingLayerId);
return ResultCode.Success;
}
[CommandCmif(50)]
// SetHandlesRequestToDisplay(b8)
public ResultCode SetHandlesRequestToDisplay(ServiceCtx context)
{
bool handlesRequestToDisplay = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { handlesRequestToDisplay });
_handlesRequestToDisplay = handlesRequestToDisplay;
return ResultCode.Success;
}
[CommandCmif(62)]
// SetIdleTimeDetectionExtension(u32)
public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context)
{
uint idleTimeDetectionExtension = context.RequestData.ReadUInt32();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { idleTimeDetectionExtension });
_idleTimeDetectionExtension = idleTimeDetectionExtension;
return ResultCode.Success;
}
[CommandCmif(63)]
// GetIdleTimeDetectionExtension() -> u32
public ResultCode GetIdleTimeDetectionExtension(ServiceCtx context)
{
context.ResponseData.Write(_idleTimeDetectionExtension);
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension });
return ResultCode.Success;
}
[CommandCmif(65)]
// ReportUserIsActive()
public ResultCode ReportUserIsActive(ServiceCtx context)
{
// TODO: Call idle:sys ReportUserIsActive when implemented.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(67)] //3.0.0+
// IsIlluminanceAvailable() -> bool
public ResultCode IsIlluminanceAvailable(ServiceCtx context)
{
// NOTE: This should call IsAmbientLightSensorAvailable through to Lbl, but there's no situation where we'd want false.
context.ResponseData.Write(true);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(68)]
// SetAutoSleepDisabled(u8)
public ResultCode SetAutoSleepDisabled(ServiceCtx context)
{
bool autoSleepDisabled = context.RequestData.ReadBoolean();
_autoSleepDisabled = autoSleepDisabled;
return ResultCode.Success;
}
[CommandCmif(69)]
// IsAutoSleepDisabled() -> u8
public ResultCode IsAutoSleepDisabled(ServiceCtx context)
{
context.ResponseData.Write(_autoSleepDisabled);
return ResultCode.Success;
}
[CommandCmif(71)] //5.0.0+
// GetCurrentIlluminanceEx() -> (bool, f32)
public ResultCode GetCurrentIlluminanceEx(ServiceCtx context)
{
// TODO: The light value should be configurable - presumably users using software that takes advantage will want control.
context.ResponseData.Write(1); // OverLimit
context.ResponseData.Write(10000f); // Lux - 10K lux is ambient light.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(80)] // 4.0.0+
// SetWirelessPriorityMode(s32 wireless_priority_mode)
public ResultCode SetWirelessPriorityMode(ServiceCtx context)
{
WirelessPriorityMode wirelessPriorityMode = (WirelessPriorityMode)context.RequestData.ReadInt32();
if (wirelessPriorityMode > WirelessPriorityMode.Unknown2)
{
return ResultCode.InvalidParameters;
}
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { wirelessPriorityMode });
return ResultCode.Success;
}
[CommandCmif(90)] // 6.0.0+
// GetAccumulatedSuspendedTickValue() -> u64
public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context)
{
context.ResponseData.Write(_accumulatedSuspendedTickValue);
return ResultCode.Success;
}
[CommandCmif(91)] // 6.0.0+
// GetAccumulatedSuspendedTickChangedEvent() -> handle<copy>
public ResultCode GetAccumulatedSuspendedTickChangedEvent(ServiceCtx context)
{
if (_accumulatedSuspendedTickChangedEventHandle == 0)
{
_accumulatedSuspendedTickChangedEvent = new KEvent(context.Device.System.KernelContext);
_accumulatedSuspendedTickChangedEvent.ReadableEvent.Signal();
if (context.Process.HandleTable.GenerateHandle(_accumulatedSuspendedTickChangedEvent.ReadableEvent, out _accumulatedSuspendedTickChangedEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_accumulatedSuspendedTickChangedEventHandle);
return ResultCode.Success;
}
[CommandCmif(100)] // 7.0.0+
// SetAlbumImageTakenNotificationEnabled(u8)
public ResultCode SetAlbumImageTakenNotificationEnabled(ServiceCtx context)
{
bool albumImageTakenNotificationEnabled = context.RequestData.ReadBoolean();
_albumImageTakenNotificationEnabled = albumImageTakenNotificationEnabled;
return ResultCode.Success;
}
[CommandCmif(120)] // 11.0.0+
// SaveCurrentScreenshot(s32 album_report_option)
public ResultCode SaveCurrentScreenshot(ServiceCtx context)
{
AlbumReportOption albumReportOption = (AlbumReportOption)context.RequestData.ReadInt32();
if (albumReportOption > AlbumReportOption.Unknown3)
{
return ResultCode.InvalidParameters;
}
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption });
return ResultCode.Success;
}
[CommandCmif(130)] // 13.0.0+
// SetRecordVolumeMuted(b8)
public ResultCode SetRecordVolumeMuted(ServiceCtx context)
{
bool recordVolumeMuted = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted });
_recordVolumeMuted = recordVolumeMuted;
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,36 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
class IWindowController : IpcService
{
private readonly ulong _pid;
public IWindowController(ulong pid)
{
_pid = pid;
}
[CommandCmif(1)]
// GetAppletResourceUserId() -> nn::applet::AppletResourceUserId
public ResultCode GetAppletResourceUserId(ServiceCtx context)
{
long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid);
context.ResponseData.Write(appletResourceUserId);
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId });
return ResultCode.Success;
}
[CommandCmif(10)]
// AcquireForegroundRights()
public ResultCode AcquireForegroundRights(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types
{
enum AlbumReportOption
{
OverlayNotDisplayed,
OverlayDisplayed,
Unknown2,
Unknown3
}
}

View file

@ -0,0 +1,36 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
{
enum AppletMessage
{
None = 0,
ChangeIntoForeground = 1,
ChangeIntoBackground = 2,
Exit = 4,
ApplicationExited = 6,
FocusStateChanged = 15,
Resume = 16,
DetectShortPressingHomeButton = 20,
DetectLongPressingHomeButton = 21,
DetectShortPressingPowerButton = 22,
DetectMiddlePressingPowerButton = 23,
DetectLongPressingPowerButton = 24,
RequestToPrepareSleep = 25,
FinishedSleepSequence = 26,
SleepRequiredByHighTemperature = 27,
SleepRequiredByLowBattery = 28,
AutoPowerDown = 29,
OperationModeChanged = 30,
PerformanceModeChanged = 31,
DetectReceivingCecSystemStandby = 32,
SdCardRemoved = 33,
LaunchApplicationRequested = 50,
RequestToDisplay = 51,
ShowApplicationLogo = 55,
HideApplicationLogo = 56,
ForceHideApplicationLogo = 57,
FloatingApplicationDetected = 60,
DetectShortPressingCaptureButton = 90,
AlbumScreenShotTaken = 92,
AlbumRecordingSaved = 93
}
}

View file

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

View file

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

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types
{
enum WirelessPriorityMode
{
Default,
OptimizedForWlan,
Unknown2
}
}

View file

@ -0,0 +1,120 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
internal class AppletFifo<T> : IAppletFifo<T>
{
private ConcurrentQueue<T> _dataQueue;
public event EventHandler DataAvailable;
public bool IsSynchronized
{
get { return ((ICollection)_dataQueue).IsSynchronized; }
}
public object SyncRoot
{
get { return ((ICollection)_dataQueue).SyncRoot; }
}
public int Count
{
get { return _dataQueue.Count; }
}
public AppletFifo()
{
_dataQueue = new ConcurrentQueue<T>();
}
public void Push(T item)
{
_dataQueue.Enqueue(item);
DataAvailable?.Invoke(this, null);
}
public bool TryAdd(T item)
{
try
{
this.Push(item);
return true;
}
catch
{
return false;
}
}
public T Pop()
{
if (_dataQueue.TryDequeue(out T result))
{
return result;
}
throw new InvalidOperationException("FIFO empty.");
}
public bool TryPop(out T result)
{
return _dataQueue.TryDequeue(out result);
}
public bool TryTake(out T item)
{
return this.TryPop(out item);
}
public T Peek()
{
if (_dataQueue.TryPeek(out T result))
{
return result;
}
throw new InvalidOperationException("FIFO empty.");
}
public bool TryPeek(out T result)
{
return _dataQueue.TryPeek(out result);
}
public void Clear()
{
_dataQueue.Clear();
}
public T[] ToArray()
{
return _dataQueue.ToArray();
}
public void CopyTo(T[] array, int arrayIndex)
{
_dataQueue.CopyTo(array, arrayIndex);
}
public void CopyTo(Array array, int index)
{
this.CopyTo((T[])array, index);
}
public IEnumerator<T> GetEnumerator()
{
return _dataQueue.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _dataQueue.GetEnumerator();
}
}
}

View file

@ -0,0 +1,77 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
internal class AppletSession
{
private IAppletFifo<byte[]> _inputData;
private IAppletFifo<byte[]> _outputData;
public event EventHandler DataAvailable;
public int Length
{
get { return _inputData.Count; }
}
public AppletSession()
: this(new AppletFifo<byte[]>(),
new AppletFifo<byte[]>())
{ }
public AppletSession(
IAppletFifo<byte[]> inputData,
IAppletFifo<byte[]> outputData)
{
_inputData = inputData;
_outputData = outputData;
_inputData.DataAvailable += OnDataAvailable;
}
private void OnDataAvailable(object sender, EventArgs e)
{
DataAvailable?.Invoke(this, null);
}
public void Push(byte[] item)
{
if (!this.TryPush(item))
{
// TODO(jduncanator): Throw a proper exception
throw new InvalidOperationException();
}
}
public bool TryPush(byte[] item)
{
return _outputData.TryAdd(item);
}
public byte[] Pop()
{
if (this.TryPop(out byte[] item))
{
return item;
}
throw new InvalidOperationException("Input data empty.");
}
public bool TryPop(out byte[] item)
{
return _inputData.TryTake(out item);
}
/// <summary>
/// This returns an AppletSession that can be used at the
/// other end of the pipe. Pushing data into this new session
/// will put it in the first session's input buffer, and vice
/// versa.
/// </summary>
public AppletSession GetConsumer()
{
return new AppletSession(this._outputData, this._inputData);
}
}
}

View file

@ -0,0 +1,29 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[Service("appletAE")]
class IAllSystemAppletProxiesService : IpcService
{
public IAllSystemAppletProxiesService(ServiceCtx context) { }
[CommandCmif(100)]
// OpenSystemAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ISystemAppletProxy>
public ResultCode OpenSystemAppletProxy(ServiceCtx context)
{
MakeObject(context, new ISystemAppletProxy(context.Request.HandleDesc.PId));
return ResultCode.Success;
}
[CommandCmif(200)]
[CommandCmif(201)] // 3.0.0+
// OpenLibraryAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ILibraryAppletProxy>
public ResultCode OpenLibraryAppletProxy(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletProxy(context.Request.HandleDesc.PId));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,10 @@
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
interface IAppletFifo<T> : IProducerConsumerCollection<T>
{
event EventHandler DataAvailable;
}
}

View file

@ -0,0 +1,23 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
class IStorage : IpcService
{
public bool IsReadOnly { get; private set; }
public byte[] Data { get; private set; }
public IStorage(byte[] data, bool isReadOnly = false)
{
IsReadOnly = isReadOnly;
Data = data;
}
[CommandCmif(0)]
// Open() -> object<nn::am::service::IStorageAccessor>
public ResultCode Open(ServiceCtx context)
{
MakeObject(context, new IStorageAccessor(this));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,86 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
class IStorageAccessor : IpcService
{
private IStorage _storage;
public IStorageAccessor(IStorage storage)
{
_storage = storage;
}
[CommandCmif(0)]
// GetSize() -> u64
public ResultCode GetSize(ServiceCtx context)
{
context.ResponseData.Write((long)_storage.Data.Length);
return ResultCode.Success;
}
[CommandCmif(10)]
// Write(u64, buffer<bytes, 0x21>)
public ResultCode Write(ServiceCtx context)
{
if (_storage.IsReadOnly)
{
return ResultCode.ObjectInvalid;
}
ulong writePosition = context.RequestData.ReadUInt64();
if (writePosition > (ulong)_storage.Data.Length)
{
return ResultCode.OutOfBounds;
}
(ulong position, ulong size) = context.Request.GetBufferType0x21();
size = Math.Min(size, (ulong)_storage.Data.Length - writePosition);
if (size > 0)
{
ulong maxSize = (ulong)_storage.Data.Length - writePosition;
if (size > maxSize)
{
size = maxSize;
}
byte[] data = new byte[size];
context.Memory.Read(position, data);
Buffer.BlockCopy(data, 0, _storage.Data, (int)writePosition, (int)size);
}
return ResultCode.Success;
}
[CommandCmif(11)]
// Read(u64) -> buffer<bytes, 0x22>
public ResultCode Read(ServiceCtx context)
{
ulong readPosition = context.RequestData.ReadUInt64();
if (readPosition > (ulong)_storage.Data.Length)
{
return ResultCode.OutOfBounds;
}
(ulong position, ulong size) = context.Request.GetBufferType0x22();
size = Math.Min(size, (ulong)_storage.Data.Length - readPosition);
byte[] data = new byte[size];
Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size);
context.Memory.Write(position, data);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,28 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.IO;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
{
class StorageHelper
{
private const uint LaunchParamsMagic = 0xc79497ca;
public static byte[] MakeLaunchParams(UserProfile userProfile)
{
// Size needs to be at least 0x88 bytes otherwise application errors.
using (MemoryStream ms = MemoryStreamManager.Shared.GetStream())
{
BinaryWriter writer = new BinaryWriter(ms);
ms.SetLength(0x88);
writer.Write(LaunchParamsMagic);
writer.Write(1); // IsAccountSelected? Only lower 8 bits actually used.
userProfile.UserId.Write(writer);
return ms.ToArray();
}
}
}
}

View file

@ -0,0 +1,27 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
enum AppletId
{
Application = 0x01,
OverlayDisplay = 0x02,
QLaunch = 0x03,
Starter = 0x04,
Auth = 0x0A,
Cabinet = 0x0B,
Controller = 0x0C,
DataErase = 0x0D,
Error = 0x0E,
NetConnect = 0x0F,
PlayerSelect = 0x10,
SoftwareKeyboard = 0x11,
MiiEdit = 0x12,
LibAppletWeb = 0x13,
LibAppletShop = 0x14,
PhotoViewer = 0x15,
Settings = 0x16,
LibAppletOff = 0x17,
LibAppletWhitelisted = 0x18,
LibAppletAuth = 0x19,
MyPage = 0x1A
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct AppletIdentifyInfo
{
public AppletId AppletId;
public uint Padding;
public ulong TitleId;
}
}

View file

@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
struct AppletProcessLaunchReason
{
public byte Flag;
public ushort Unknown1;
public byte Unknown2;
}
}

View file

@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[StructLayout(LayoutKind.Sequential, Size = 0x8)]
struct LibraryAppletInfo
{
public AppletId AppletId;
public LibraryAppletMode LibraryAppletMode;
}
}

View file

@ -0,0 +1,14 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
[Flags]
enum LibraryAppletMode : uint
{
AllForeground,
PartialForeground,
NoUi,
PartialForegroundWithIndirectDisplay,
AllForegroundInitiallyHidden
}
}

View file

@ -0,0 +1,675 @@
using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Ncm;
using LibHac.Ns;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Horizon.Common;
using System;
using System.Numerics;
using System.Threading;
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
using ApplicationId = LibHac.Ncm.ApplicationId;
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy
{
class IApplicationFunctions : IpcService
{
private long _defaultSaveDataSize = 200000000;
private long _defaultJournalSaveDataSize = 200000000;
private KEvent _gpuErrorDetectedSystemEvent;
private KEvent _friendInvitationStorageChannelEvent;
private KEvent _notificationStorageChannelEvent;
private KEvent _healthWarningDisappearedSystemEvent;
private int _gpuErrorDetectedSystemEventHandle;
private int _friendInvitationStorageChannelEventHandle;
private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle;
private bool _gamePlayRecordingState;
private int _jitLoaded;
private LibHac.HorizonClient _horizon;
public IApplicationFunctions(Horizon system)
{
// TODO: Find where they are signaled.
_gpuErrorDetectedSystemEvent = new KEvent(system.KernelContext);
_friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
_notificationStorageChannelEvent = new KEvent(system.KernelContext);
_healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
_horizon = system.LibHacHorizonManager.AmClient;
}
[CommandCmif(1)]
// PopLaunchParameter(LaunchParameterKind kind) -> object<nn::am::service::IStorage>
public ResultCode PopLaunchParameter(ServiceCtx context)
{
LaunchParameterKind kind = (LaunchParameterKind)context.RequestData.ReadUInt32();
byte[] storageData;
switch (kind)
{
case LaunchParameterKind.UserChannel:
storageData = context.Device.Configuration.UserChannelPersistence.Pop();
break;
case LaunchParameterKind.PreselectedUser:
// Only the first 0x18 bytes of the Data seems to be actually used.
storageData = StorageHelper.MakeLaunchParams(context.Device.System.AccountManager.LastOpenedUser);
break;
case LaunchParameterKind.Unknown:
throw new NotImplementedException("Unknown LaunchParameterKind.");
default:
return ResultCode.ObjectInvalid;
}
if (storageData == null)
{
return ResultCode.NotAvailable;
}
MakeObject(context, new AppletAE.IStorage(storageData));
return ResultCode.Success;
}
[CommandCmif(12)] // 4.0.0+
// CreateApplicationAndRequestToStart(u64 title_id)
public ResultCode CreateApplicationAndRequestToStart(ServiceCtx context)
{
ulong titleId = context.RequestData.ReadUInt64();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { titleId });
if (titleId == 0)
{
context.Device.UiHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId);
}
else
{
throw new NotImplementedException();
}
return ResultCode.Success;
}
[CommandCmif(20)]
// EnsureSaveData(nn::account::Uid) -> u64
public ResultCode EnsureSaveData(ServiceCtx context)
{
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
context.ResponseData.Write(requiredSize);
return (ResultCode)result.Value;
}
[CommandCmif(21)]
// GetDesiredLanguage() -> nn::settings::LanguageCode
public ResultCode GetDesiredLanguage(ServiceCtx context)
{
// This seems to be calling ns:am GetApplicationDesiredLanguage followed by ConvertApplicationLanguageToLanguageCode
// Calls are from a IReadOnlyApplicationControlDataInterface object
// ConvertApplicationLanguageToLanguageCode compares language code strings and returns the index
// TODO: When above calls are implemented, switch to using ns:am
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
{
Logger.Warning?.Print(LogClass.ServiceAm, "Application has zero supported languages");
context.ResponseData.Write(desiredLanguageCode);
return ResultCode.Success;
}
// If desired language is not supported by application, use first supported language from TitleLanguage.
// TODO: In the future, a GUI could enable user-specified search priority
if (((1 << (int)context.Device.System.State.DesiredTitleLanguage) & supportedLanguages) == 0)
{
SystemLanguage newLanguage = Enum.Parse<SystemLanguage>(Enum.GetName(typeof(TitleLanguage), firstSupported));
desiredLanguageCode = SystemStateMgr.GetLanguageCode((int)newLanguage);
Logger.Info?.Print(LogClass.ServiceAm, $"Application doesn't support configured language. Using {newLanguage}");
}
context.ResponseData.Write(desiredLanguageCode);
return ResultCode.Success;
}
[CommandCmif(22)]
// SetTerminateResult(u32)
public ResultCode SetTerminateResult(ServiceCtx context)
{
LibHac.Result result = new LibHac.Result(context.RequestData.ReadUInt32());
Logger.Info?.Print(LogClass.ServiceAm, $"Result = 0x{result.Value:x8} ({result.ToStringWithName()}).");
return ResultCode.Success;
}
[CommandCmif(23)]
// GetDisplayVersion() -> nn::oe::DisplayVersion
public ResultCode GetDisplayVersion(ServiceCtx context)
{
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
return ResultCode.Success;
}
[CommandCmif(25)] // 3.0.0+
// ExtendSaveData(u8 save_data_type, nn::account::Uid, s64 save_size, s64 journal_size) -> u64 result_code
public ResultCode ExtendSaveData(ServiceCtx context)
{
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
Uid userId = context.RequestData.ReadStruct<Uid>();
long saveDataSize = context.RequestData.ReadInt64();
long journalSize = context.RequestData.ReadInt64();
// NOTE: Service calls nn::fs::ExtendApplicationSaveData.
// Since LibHac currently doesn't support this method, we can stub it for now.
_defaultSaveDataSize = saveDataSize;
_defaultJournalSaveDataSize = journalSize;
context.ResponseData.Write((uint)ResultCode.Success);
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId, saveDataSize, journalSize });
return ResultCode.Success;
}
[CommandCmif(26)] // 3.0.0+
// GetSaveDataSize(u8 save_data_type, nn::account::Uid) -> (s64 save_size, s64 journal_size)
public ResultCode GetSaveDataSize(ServiceCtx context)
{
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
Uid userId = context.RequestData.ReadStruct<Uid>();
// NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded.
// Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes.
// Since LibHac currently doesn't support the 2 last methods, we can hardcode the values to 200mb.
context.ResponseData.Write(_defaultSaveDataSize);
context.ResponseData.Write(_defaultJournalSaveDataSize);
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId });
return ResultCode.Success;
}
[CommandCmif(27)] // 5.0.0+
// CreateCacheStorage(u16 index, s64 save_size, s64 journal_size) -> (u32 storageTarget, u64 requiredSize)
public ResultCode CreateCacheStorage(ServiceCtx context)
{
ushort index = (ushort)context.RequestData.ReadUInt64();
long saveSize = context.RequestData.ReadInt64();
long journalSize = context.RequestData.ReadInt64();
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);
if (result.IsFailure()) return (ResultCode)result.Value;
context.ResponseData.Write((ulong)storageTarget);
context.ResponseData.Write(requiredSize);
return ResultCode.Success;
}
[CommandCmif(28)] // 11.0.0+
// GetSaveDataSizeMax() -> (s64 save_size_max, s64 journal_size_max)
public ResultCode GetSaveDataSizeMax(ServiceCtx context)
{
// NOTE: We are currently using a stub for GetSaveDataSize() which returns the default values.
// For this method we shouldn't return anything lower than that, but since we aren't interacting
// with fs to get the actual sizes, we return the default values here as well.
// This also helps in case ExtendSaveData() has been executed and the default values were modified.
context.ResponseData.Write(_defaultSaveDataSize);
context.ResponseData.Write(_defaultJournalSaveDataSize);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(30)]
// BeginBlockingHomeButtonShortAndLongPressed()
public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context)
{
// NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 1 then it signals an internal event.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(31)]
// EndBlockingHomeButtonShortAndLongPressed()
public ResultCode EndBlockingHomeButtonShortAndLongPressed(ServiceCtx context)
{
// NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 0 then it signals an internal event.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(32)] // 2.0.0+
// BeginBlockingHomeButton(u64 nano_second)
public ResultCode BeginBlockingHomeButton(ServiceCtx context)
{
ulong nanoSeconds = context.RequestData.ReadUInt64();
// NOTE: This set two internal fields at offsets 0x89 to value 1 and 0x90 to value of "nanoSeconds" then it signals an internal event.
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { nanoSeconds });
return ResultCode.Success;
}
[CommandCmif(33)] // 2.0.0+
// EndBlockingHomeButton()
public ResultCode EndBlockingHomeButton(ServiceCtx context)
{
// NOTE: This set two internal fields at offsets 0x89 and 0x90 to value 0 then it signals an internal event.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(40)]
// NotifyRunning() -> b8
public ResultCode NotifyRunning(ServiceCtx context)
{
context.ResponseData.Write(true);
return ResultCode.Success;
}
[CommandCmif(50)] // 2.0.0+
// GetPseudoDeviceId() -> nn::util::Uuid
public ResultCode GetPseudoDeviceId(ServiceCtx context)
{
context.ResponseData.Write(0L);
context.ResponseData.Write(0L);
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(60)] // 2.0.0+
// SetMediaPlaybackStateForApplication(bool enabled)
public ResultCode SetMediaPlaybackStateForApplication(ServiceCtx context)
{
bool enabled = context.RequestData.ReadBoolean();
// NOTE: Service stores the "enabled" value in a private field, when enabled is false, it stores nn::os::GetSystemTick() too.
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { enabled });
return ResultCode.Success;
}
[CommandCmif(65)] // 3.0.0+
// IsGamePlayRecordingSupported() -> u8
public ResultCode IsGamePlayRecordingSupported(ServiceCtx context)
{
context.ResponseData.Write(_gamePlayRecordingState);
return ResultCode.Success;
}
[CommandCmif(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
[CommandCmif(67)] // 3.0.0+
// SetGamePlayRecordingState(u32)
public ResultCode SetGamePlayRecordingState(ServiceCtx context)
{
_gamePlayRecordingState = context.RequestData.ReadInt32() != 0;
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState });
return ResultCode.Success;
}
[CommandCmif(90)] // 4.0.0+
// EnableApplicationCrashReport(u8)
public ResultCode EnableApplicationCrashReport(ServiceCtx context)
{
bool applicationCrashReportEnabled = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { applicationCrashReportEnabled });
return ResultCode.Success;
}
[CommandCmif(100)] // 5.0.0+
// InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle<copy, transfer_memory> transfer_memory, u64 transfer_memory_size)
public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context)
{
int width = context.RequestData.ReadInt32();
int height = context.RequestData.ReadInt32();
ulong transferMemorySize = context.RequestData.ReadUInt64();
int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0];
ulong transferMemoryAddress = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle).Address;
ResultCode resultCode = ResultCode.InvalidParameters;
if (((transferMemorySize & 0x3FFFF) == 0) && width <= 1280 && height <= 720)
{
resultCode = InitializeApplicationCopyrightFrameBufferImpl(transferMemoryAddress, transferMemorySize, width, height);
}
if (transferMemoryHandle != 0)
{
context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle);
}
return resultCode;
}
private ResultCode InitializeApplicationCopyrightFrameBufferImpl(ulong transferMemoryAddress, ulong transferMemorySize, int width, int height)
{
if ((transferMemorySize & 0x3FFFF) != 0)
{
return ResultCode.InvalidParameters;
}
ResultCode resultCode;
// if (_copyrightBuffer == null)
{
// TODO: Initialize buffer and object.
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { transferMemoryAddress, transferMemorySize, width, height });
resultCode = ResultCode.Success;
}
return resultCode;
}
[CommandCmif(101)] // 5.0.0+
// SetApplicationCopyrightImage(buffer<bytes, 0x45> frame_buffer, s32 x, s32 y, s32 width, s32 height, s32 window_origin_mode)
public ResultCode SetApplicationCopyrightImage(ServiceCtx context)
{
ulong frameBufferPos = context.Request.SendBuff[0].Position;
ulong frameBufferSize = context.Request.SendBuff[0].Size;
int x = context.RequestData.ReadInt32();
int y = context.RequestData.ReadInt32();
int width = context.RequestData.ReadInt32();
int height = context.RequestData.ReadInt32();
uint windowOriginMode = context.RequestData.ReadUInt32();
ResultCode resultCode = ResultCode.InvalidParameters;
if (((y | x) >= 0) && width >= 1 && height >= 1)
{
ResultCode result = SetApplicationCopyrightImageImpl(x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode);
if (result != ResultCode.Success)
{
resultCode = result;
}
else
{
resultCode = ResultCode.Success;
}
}
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { frameBufferPos, frameBufferSize, x, y, width, height, windowOriginMode });
return resultCode;
}
private ResultCode SetApplicationCopyrightImageImpl(int x, int y, int width, int height, ulong frameBufferPos, ulong frameBufferSize, uint windowOriginMode)
{
/*
if (_copyrightBuffer == null)
{
return ResultCode.NullCopyrightObject;
}
*/
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode });
return ResultCode.Success;
}
[CommandCmif(102)] // 5.0.0+
// SetApplicationCopyrightVisibility(bool visible)
public ResultCode SetApplicationCopyrightVisibility(ServiceCtx context)
{
bool visible = context.RequestData.ReadBoolean();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { visible });
// NOTE: It sets an internal field and return ResultCode.Success in all case.
return ResultCode.Success;
}
[CommandCmif(110)] // 5.0.0+
// QueryApplicationPlayStatistics(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
public ResultCode QueryApplicationPlayStatistics(ServiceCtx context)
{
// TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented.
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context);
}
[CommandCmif(111)] // 6.0.0+
// QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count)
public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context)
{
// TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented.
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
}
[CommandCmif(120)] // 5.0.0+
// ExecuteProgram(ProgramSpecifyKind kind, u64 value)
public ResultCode ExecuteProgram(ServiceCtx context)
{
ProgramSpecifyKind kind = (ProgramSpecifyKind)context.RequestData.ReadUInt32();
// padding
context.RequestData.ReadUInt32();
ulong value = context.RequestData.ReadUInt64();
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value });
context.Device.UiHandler.ExecuteProgram(context.Device, kind, value);
return ResultCode.Success;
}
[CommandCmif(121)] // 5.0.0+
// ClearUserChannel()
public ResultCode ClearUserChannel(ServiceCtx context)
{
context.Device.Configuration.UserChannelPersistence.Clear();
return ResultCode.Success;
}
[CommandCmif(122)] // 5.0.0+
// UnpopToUserChannel(object<nn::am::service::IStorage> input_storage)
public ResultCode UnpopToUserChannel(ServiceCtx context)
{
AppletAE.IStorage data = GetObject<AppletAE.IStorage>(context, 0);
context.Device.Configuration.UserChannelPersistence.Push(data.Data);
return ResultCode.Success;
}
[CommandCmif(123)] // 5.0.0+
// GetPreviousProgramIndex() -> s32 program_index
public ResultCode GetPreviousProgramIndex(ServiceCtx context)
{
int previousProgramIndex = context.Device.Configuration.UserChannelPersistence.PreviousIndex;
context.ResponseData.Write(previousProgramIndex);
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { previousProgramIndex });
return ResultCode.Success;
}
[CommandCmif(130)] // 8.0.0+
// GetGpuErrorDetectedSystemEvent() -> handle<copy>
public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context)
{
if (_gpuErrorDetectedSystemEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_gpuErrorDetectedSystemEvent.ReadableEvent, out _gpuErrorDetectedSystemEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle);
// NOTE: This is used by "sdk" NSO during applet-application initialization.
// A separate thread is setup where event-waiting is handled.
// When the Event is signaled, official sw will assert.
return ResultCode.Success;
}
[CommandCmif(140)] // 9.0.0+
// GetFriendInvitationStorageChannelEvent() -> handle<copy>
public ResultCode GetFriendInvitationStorageChannelEvent(ServiceCtx context)
{
if (_friendInvitationStorageChannelEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_friendInvitationStorageChannelEvent.ReadableEvent, out _friendInvitationStorageChannelEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_friendInvitationStorageChannelEventHandle);
return ResultCode.Success;
}
[CommandCmif(141)] // 9.0.0+
// TryPopFromFriendInvitationStorageChannel() -> object<nn::am::service::IStorage>
public ResultCode TryPopFromFriendInvitationStorageChannel(ServiceCtx context)
{
// NOTE: IStorage are pushed in the channel with IApplicationAccessor PushToFriendInvitationStorageChannel
// If _friendInvitationStorageChannelEvent is signaled, the event is cleared.
// If an IStorage is available, returns it with ResultCode.Success.
// If not, just returns ResultCode.NotAvailable. Since we don't support friend feature for now, it's fine to do the same.
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.NotAvailable;
}
[CommandCmif(150)] // 9.0.0+
// GetNotificationStorageChannelEvent() -> handle<copy>
public ResultCode GetNotificationStorageChannelEvent(ServiceCtx context)
{
if (_notificationStorageChannelEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_notificationStorageChannelEvent.ReadableEvent, out _notificationStorageChannelEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationStorageChannelEventHandle);
return ResultCode.Success;
}
[CommandCmif(160)] // 9.0.0+
// GetHealthWarningDisappearedSystemEvent() -> handle<copy>
public ResultCode GetHealthWarningDisappearedSystemEvent(ServiceCtx context)
{
if (_healthWarningDisappearedSystemEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_healthWarningDisappearedSystemEvent.ReadableEvent, out _healthWarningDisappearedSystemEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_healthWarningDisappearedSystemEventHandle);
return ResultCode.Success;
}
[CommandCmif(1001)] // 10.0.0+
// PrepareForJit()
public ResultCode PrepareForJit(ServiceCtx context)
{
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
{
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath);
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
}
context.Device.LoadNca(filePath);
// FIXME: Most likely not how this should be done?
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
{
context.Device.System.SmRegistry.WaitForServiceRegistration();
}
}
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types
{
public enum LaunchParameterKind : uint
{
UserChannel = 1,
PreselectedUser,
Unknown
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types
{
public enum ProgramSpecifyKind : uint
{
ExecuteProgram,
SubApplicationProgram,
RestartProgram
}
}

View file

@ -0,0 +1,87 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy;
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
{
class IApplicationProxy : IpcService
{
private readonly ulong _pid;
public IApplicationProxy(ulong pid)
{
_pid = pid;
}
[CommandCmif(0)]
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
public ResultCode GetCommonStateGetter(ServiceCtx context)
{
MakeObject(context, new ICommonStateGetter(context));
return ResultCode.Success;
}
[CommandCmif(1)]
// GetSelfController() -> object<nn::am::service::ISelfController>
public ResultCode GetSelfController(ServiceCtx context)
{
MakeObject(context, new ISelfController(context, _pid));
return ResultCode.Success;
}
[CommandCmif(2)]
// GetWindowController() -> object<nn::am::service::IWindowController>
public ResultCode GetWindowController(ServiceCtx context)
{
MakeObject(context, new IWindowController(_pid));
return ResultCode.Success;
}
[CommandCmif(3)]
// GetAudioController() -> object<nn::am::service::IAudioController>
public ResultCode GetAudioController(ServiceCtx context)
{
MakeObject(context, new IAudioController());
return ResultCode.Success;
}
[CommandCmif(4)]
// GetDisplayController() -> object<nn::am::service::IDisplayController>
public ResultCode GetDisplayController(ServiceCtx context)
{
MakeObject(context, new IDisplayController(context));
return ResultCode.Success;
}
[CommandCmif(11)]
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
{
MakeObject(context, new ILibraryAppletCreator());
return ResultCode.Success;
}
[CommandCmif(20)]
// GetApplicationFunctions() -> object<nn::am::service::IApplicationFunctions>
public ResultCode GetApplicationFunctions(ServiceCtx context)
{
MakeObject(context, new IApplicationFunctions(context.Device.System));
return ResultCode.Success;
}
[CommandCmif(1000)]
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
public ResultCode GetDebugFunctions(ServiceCtx context)
{
MakeObject(context, new IDebugFunctions());
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,19 @@
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService;
namespace Ryujinx.HLE.HOS.Services.Am
{
[Service("appletOE")]
class IApplicationProxyService : IpcService
{
public IApplicationProxyService(ServiceCtx context) { }
[CommandCmif(0)]
// OpenApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy>
public ResultCode OpenApplicationProxy(ServiceCtx context)
{
MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Am.Idle
{
[Service("idle:sys")]
class IPolicyManagerSystem : IpcService
{
public IPolicyManagerSystem(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Am.Omm
{
[Service("omm")]
class IOperationModeManager : IpcService
{
public IOperationModeManager(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,30 @@
namespace Ryujinx.HLE.HOS.Services.Am
{
enum ResultCode
{
ModuleId = 128,
ErrorCodeShift = 9,
Success = 0,
NotAvailable = (2 << ErrorCodeShift) | ModuleId,
NoMessages = (3 << ErrorCodeShift) | ModuleId,
AppletLaunchFailed = (35 << ErrorCodeShift) | ModuleId,
TitleIdNotFound = (37 << ErrorCodeShift) | ModuleId,
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
IStorageInUse = (502 << ErrorCodeShift) | ModuleId,
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
BufferNotAcquired = (504 << ErrorCodeShift) | ModuleId,
BufferAlreadyAcquired = (505 << ErrorCodeShift) | ModuleId,
InvalidParameters = (506 << ErrorCodeShift) | ModuleId,
OpenedAsWrongType = (511 << ErrorCodeShift) | ModuleId,
UnbalancedFatalSection = (512 << ErrorCodeShift) | ModuleId,
NullObject = (518 << ErrorCodeShift) | ModuleId,
MemoryAllocationFailed = (600 << ErrorCodeShift) | ModuleId,
StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId,
DebugModeNotEnabled = (974 << ErrorCodeShift) | ModuleId,
DevFunctionNotEnabled = (980 << ErrorCodeShift) | ModuleId,
NotImplemented = (998 << ErrorCodeShift) | ModuleId,
Stubbed = (999 << ErrorCodeShift) | ModuleId
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Am.Spsm
{
[Service("spsm")]
class IPowerStateInterface : IpcService
{
public IPowerStateInterface(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Am.Tcap
{
[Service("tcap")]
class IManager : IpcService
{
public IManager(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,43 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
abstract class IManager : IpcService
{
public IManager(ServiceCtx context) { }
protected abstract ResultCode OpenSession(out SessionServer sessionServer);
protected abstract PerformanceMode GetPerformanceMode();
protected abstract bool IsCpuOverclockEnabled();
[CommandCmif(0)]
// OpenSession() -> object<nn::apm::ISession>
public ResultCode OpenSession(ServiceCtx context)
{
ResultCode resultCode = OpenSession(out SessionServer sessionServer);
if (resultCode == ResultCode.Success)
{
MakeObject(context, sessionServer);
}
return resultCode;
}
[CommandCmif(1)]
// GetPerformanceMode() -> nn::apm::PerformanceMode
public ResultCode GetPerformanceMode(ServiceCtx context)
{
context.ResponseData.Write((uint)GetPerformanceMode());
return ResultCode.Success;
}
[CommandCmif(6)] // 7.0.0+
// IsCpuOverclockEnabled() -> bool
public ResultCode IsCpuOverclockEnabled(ServiceCtx context)
{
context.ResponseData.Write(IsCpuOverclockEnabled());
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,19 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
// NOTE: This service doesnt exist anymore after firmware 7.0.1. But some outdated homebrew still uses it.
[Service("apm:p")] // 1.0.0-7.0.1
class IManagerPrivileged : IpcService
{
public IManagerPrivileged(ServiceCtx context) { }
[CommandCmif(0)]
// OpenSession() -> object<nn::apm::ISession>
public ResultCode OpenSession(ServiceCtx context)
{
MakeObject(context, new SessionServer(context));
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,45 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
abstract class ISession : IpcService
{
public ISession(ServiceCtx context) { }
protected abstract ResultCode SetPerformanceConfiguration(PerformanceMode performanceMode, PerformanceConfiguration performanceConfiguration);
protected abstract ResultCode GetPerformanceConfiguration(PerformanceMode performanceMode, out PerformanceConfiguration performanceConfiguration);
protected abstract void SetCpuOverclockEnabled(bool enabled);
[CommandCmif(0)]
// SetPerformanceConfiguration(nn::apm::PerformanceMode, nn::apm::PerformanceConfiguration)
public ResultCode SetPerformanceConfiguration(ServiceCtx context)
{
PerformanceMode performanceMode = (PerformanceMode)context.RequestData.ReadInt32();
PerformanceConfiguration performanceConfiguration = (PerformanceConfiguration)context.RequestData.ReadInt32();
return SetPerformanceConfiguration(performanceMode, performanceConfiguration);
}
[CommandCmif(1)]
// GetPerformanceConfiguration(nn::apm::PerformanceMode) -> nn::apm::PerformanceConfiguration
public ResultCode GetPerformanceConfiguration(ServiceCtx context)
{
PerformanceMode performanceMode = (PerformanceMode)context.RequestData.ReadInt32();
ResultCode resultCode = GetPerformanceConfiguration(performanceMode, out PerformanceConfiguration performanceConfiguration);
context.ResponseData.Write((uint)performanceConfiguration);
return resultCode;
}
[CommandCmif(2)] // 8.0.0+
// SetCpuOverclockEnabled(bool)
public ResultCode SetCpuOverclockEnabled(ServiceCtx context)
{
bool enabled = context.RequestData.ReadBoolean();
SetCpuOverclockEnabled(enabled);
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,42 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
abstract class ISystemManager : IpcService
{
public ISystemManager(ServiceCtx context) { }
protected abstract void RequestPerformanceMode(PerformanceMode performanceMode);
internal abstract void SetCpuBoostMode(CpuBoostMode cpuBoostMode);
protected abstract PerformanceConfiguration GetCurrentPerformanceConfiguration();
[CommandCmif(0)]
// RequestPerformanceMode(nn::apm::PerformanceMode)
public ResultCode RequestPerformanceMode(ServiceCtx context)
{
RequestPerformanceMode((PerformanceMode)context.RequestData.ReadInt32());
// NOTE: This call seems to overclock the system related to the PerformanceMode, since we emulate it, it's fine to do nothing instead.
return ResultCode.Success;
}
[CommandCmif(6)] // 7.0.0+
// SetCpuBoostMode(nn::apm::CpuBootMode)
public ResultCode SetCpuBoostMode(ServiceCtx context)
{
SetCpuBoostMode((CpuBoostMode)context.RequestData.ReadUInt32());
// NOTE: This call seems to overclock the system related to the CpuBoostMode, since we emulate it, it's fine to do nothing instead.
return ResultCode.Success;
}
[CommandCmif(7)] // 7.0.0+
// GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration
public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context)
{
context.ResponseData.Write((uint)GetCurrentPerformanceConfiguration());
return ResultCode.Success;
}
}
}

View file

@ -0,0 +1,31 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
[Service("apm")]
[Service("apm:am")] // 8.0.0+
class ManagerServer : IManager
{
private readonly ServiceCtx _context;
public ManagerServer(ServiceCtx context) : base(context)
{
_context = context;
}
protected override ResultCode OpenSession(out SessionServer sessionServer)
{
sessionServer = new SessionServer(_context);
return ResultCode.Success;
}
protected override PerformanceMode GetPerformanceMode()
{
return _context.Device.System.PerformanceState.PerformanceMode;
}
protected override bool IsCpuOverclockEnabled()
{
return _context.Device.System.PerformanceState.CpuOverclockEnabled;
}
}
}

View file

@ -0,0 +1,25 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
class PerformanceState
{
public PerformanceState() { }
public bool CpuOverclockEnabled = false;
public PerformanceMode PerformanceMode = PerformanceMode.Default;
public CpuBoostMode CpuBoostMode = CpuBoostMode.Disabled;
public PerformanceConfiguration DefaultPerformanceConfiguration = PerformanceConfiguration.PerformanceConfiguration7;
public PerformanceConfiguration BoostPerformanceConfiguration = PerformanceConfiguration.PerformanceConfiguration8;
public PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode performanceMode)
{
return performanceMode switch
{
PerformanceMode.Default => DefaultPerformanceConfiguration,
PerformanceMode.Boost => BoostPerformanceConfiguration,
_ => PerformanceConfiguration.PerformanceConfiguration7
};
}
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
enum ResultCode
{
ModuleId = 148,
ErrorCodeShift = 9,
Success = 0,
InvalidParameters = (1 << ErrorCodeShift) | ModuleId
}
}

View file

@ -0,0 +1,58 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Apm
{
class SessionServer : ISession
{
private readonly ServiceCtx _context;
public SessionServer(ServiceCtx context) : base(context)
{
_context = context;
}
protected override ResultCode SetPerformanceConfiguration(PerformanceMode performanceMode, PerformanceConfiguration performanceConfiguration)
{
if (performanceMode > PerformanceMode.Boost)
{
return ResultCode.InvalidParameters;
}
switch (performanceMode)
{
case PerformanceMode.Default:
_context.Device.System.PerformanceState.DefaultPerformanceConfiguration = performanceConfiguration;
break;
case PerformanceMode.Boost:
_context.Device.System.PerformanceState.BoostPerformanceConfiguration = performanceConfiguration;
break;
default:
Logger.Error?.Print(LogClass.ServiceApm, $"PerformanceMode isn't supported: {performanceMode}");
break;
}
return ResultCode.Success;
}
protected override ResultCode GetPerformanceConfiguration(PerformanceMode performanceMode, out PerformanceConfiguration performanceConfiguration)
{
if (performanceMode > PerformanceMode.Boost)
{
performanceConfiguration = 0;
return ResultCode.InvalidParameters;
}
performanceConfiguration = _context.Device.System.PerformanceState.GetCurrentPerformanceConfiguration(performanceMode);
return ResultCode.Success;
}
protected override void SetCpuOverclockEnabled(bool enabled)
{
_context.Device.System.PerformanceState.CpuOverclockEnabled = enabled;
// NOTE: This call seems to overclock the system, since we emulate it, it's fine to do nothing instead.
}
}
}

View file

@ -0,0 +1,28 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
[Service("apm:sys")]
class SystemManagerServer : ISystemManager
{
private readonly ServiceCtx _context;
public SystemManagerServer(ServiceCtx context) : base(context)
{
_context = context;
}
protected override void RequestPerformanceMode(PerformanceMode performanceMode)
{
_context.Device.System.PerformanceState.PerformanceMode = performanceMode;
}
internal override void SetCpuBoostMode(CpuBoostMode cpuBoostMode)
{
_context.Device.System.PerformanceState.CpuBoostMode = cpuBoostMode;
}
protected override PerformanceConfiguration GetCurrentPerformanceConfiguration()
{
return _context.Device.System.PerformanceState.GetCurrentPerformanceConfiguration(_context.Device.System.PerformanceState.PerformanceMode);
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
enum CpuBoostMode
{
Disabled = 0,
BoostCPU = 1, // Uses PerformanceConfiguration13 and PerformanceConfiguration14, or PerformanceConfiguration15 and PerformanceConfiguration16
ConservePower = 2 // Uses PerformanceConfiguration15 and PerformanceConfiguration16.
}
}

View file

@ -0,0 +1,22 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
enum PerformanceConfiguration : uint // Clocks are all in MHz.
{ // CPU | GPU | RAM | NOTE
PerformanceConfiguration1 = 0x00010000, // 1020 | 384 | 1600 | Only available while docked.
PerformanceConfiguration2 = 0x00010001, // 1020 | 768 | 1600 | Only available while docked.
PerformanceConfiguration3 = 0x00010002, // 1224 | 691.2 | 1600 | Only available for SDEV units.
PerformanceConfiguration4 = 0x00020000, // 1020 | 230.4 | 1600 | Only available for SDEV units.
PerformanceConfiguration5 = 0x00020001, // 1020 | 307.2 | 1600 |
PerformanceConfiguration6 = 0x00020002, // 1224 | 230.4 | 1600 |
PerformanceConfiguration7 = 0x00020003, // 1020 | 307 | 1331.2 |
PerformanceConfiguration8 = 0x00020004, // 1020 | 384 | 1331.2 |
PerformanceConfiguration9 = 0x00020005, // 1020 | 307.2 | 1065.6 |
PerformanceConfiguration10 = 0x00020006, // 1020 | 384 | 1065.6 |
PerformanceConfiguration11 = 0x92220007, // 1020 | 460.8 | 1600 |
PerformanceConfiguration12 = 0x92220008, // 1020 | 460.8 | 1331.2 |
PerformanceConfiguration13 = 0x92220009, // 1785 | 768 | 1600 | 7.0.0+
PerformanceConfiguration14 = 0x9222000A, // 1785 | 768 | 1331.2 | 7.0.0+
PerformanceConfiguration15 = 0x9222000B, // 1020 | 768 | 1600 | 7.0.0+
PerformanceConfiguration16 = 0x9222000C // 1020 | 768 | 1331.2 | 7.0.0+
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Apm
{
enum PerformanceMode : uint
{
Default = 0,
Boost = 1
}
}

View file

@ -0,0 +1,43 @@
using LibHac.Ncm;
namespace Ryujinx.HLE.HOS.Services.Arp
{
class ApplicationLaunchProperty
{
public ulong TitleId;
public int Version;
public byte BaseGameStorageId;
public byte UpdateGameStorageId;
#pragma warning disable CS0649
public short Padding;
#pragma warning restore CS0649
public static ApplicationLaunchProperty Default
{
get
{
return new ApplicationLaunchProperty
{
TitleId = 0x00,
Version = 0x00,
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
UpdateGameStorageId = (byte)StorageId.None
};
}
}
public static ApplicationLaunchProperty GetByPid(ServiceCtx context)
{
// TODO: Handle ApplicationLaunchProperty as array when pid will be supported and return the right item.
// For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented.
return new ApplicationLaunchProperty
{
TitleId = context.Device.Processes.ActiveApplication.ProgramId,
Version = 0x00,
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
UpdateGameStorageId = (byte)StorageId.None
};
}
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Arp
{
[Service("arp:r")]
class IReader : IpcService
{
public IReader(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Arp
{
[Service("arp:w")]
class IWriter : IpcService
{
public IWriter(ServiceCtx context) { }
}
}

View file

@ -0,0 +1,76 @@
using LibHac;
using LibHac.Common;
using LibHac.Ncm;
using LibHac.Ns;
using System;
using ApplicationId = LibHac.ApplicationId;
namespace Ryujinx.HLE.HOS.Services.Arp
{
class LibHacIReader : LibHac.Arp.Impl.IReader
{
public ApplicationId ApplicationId { get; set; }
public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId)
{
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
{
StorageId = StorageId.BuiltInUser,
ApplicationId = ApplicationId
};
return Result.Success;
}
public void Dispose() { }
public Result GetApplicationLaunchPropertyWithApplicationId(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ApplicationId applicationId)
{
launchProperty = new LibHac.Arp.ApplicationLaunchProperty
{
StorageId = StorageId.BuiltInUser,
ApplicationId = applicationId
};
return Result.Success;
}
public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId)
{
throw new NotImplementedException();
}
public Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, ApplicationId applicationId)
{
throw new NotImplementedException();
}
public Result GetServiceObject(out object serviceObject)
{
throw new NotImplementedException();
}
}
internal class LibHacArpServiceObject : LibHac.Sm.IServiceObject
{
private SharedRef<LibHacIReader> _serviceObject;
public LibHacArpServiceObject(ref SharedRef<LibHacIReader> serviceObject)
{
_serviceObject = SharedRef<LibHacIReader>.CreateCopy(in serviceObject);
}
public void Dispose()
{
_serviceObject.Destroy();
}
public Result GetServiceObject(ref SharedRef<IDisposable> serviceObject)
{
serviceObject.SetByCopy(in _serviceObject);
return Result.Success;
}
}
}

View file

@ -0,0 +1,108 @@
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 AudioInputSystem _system;
private uint _processHandle;
private 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)
{
return ((AudioKernelEvent)outEvent).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();
}
}
}

View file

@ -0,0 +1,204 @@
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 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();
}
}
}
}

View file

@ -0,0 +1,34 @@
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();
}
}

View file

@ -0,0 +1,41 @@
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 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;
}
}
}

View file

@ -0,0 +1,235 @@
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 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;
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
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();
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
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).
bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1;
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;
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
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;
}
}
}

View file

@ -0,0 +1,108 @@
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 AudioOutputSystem _system;
private uint _processHandle;
private 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)
{
return ((AudioKernelEvent)outEvent).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();
}
}
}

View file

@ -0,0 +1,185 @@
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 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();
}
}
}
}

View file

@ -0,0 +1,33 @@
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();
}
}

View file

@ -0,0 +1,41 @@
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 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;
}
}
}

View file

@ -0,0 +1,162 @@
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 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;
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
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();
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
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;
}
}
}

View file

@ -0,0 +1,172 @@
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 VirtualDeviceSession[] _sessions;
private ulong _appletResourceId;
private int _revision;
private bool _isUsbDeviceSupported;
private VirtualDeviceSessionRegistry _registry;
private KEvent _systemEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
{
_registry = registry;
_appletResourceId = appletResourceId;
_revision = revision;
BehaviourContext behaviourContext = new BehaviourContext();
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;
}
}
}

View file

@ -0,0 +1,320 @@
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 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;
}
}
}

View file

@ -0,0 +1,25 @@
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();
}
}
}

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