diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services')
754 files changed, 62752 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs new file mode 100644 index 00000000..f5364329 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs new file mode 100644 index 00000000..535779d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs new file mode 100644 index 00000000..9c058cb5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs new file mode 100644 index 00000000..ecd51687 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs new file mode 100644 index 00000000..14911dfb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs new file mode 100644 index 00000000..64b6070f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs new file mode 100644 index 00000000..97240311 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs new file mode 100644 index 00000000..8e29f94b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs new file mode 100644 index 00000000..d9f9864a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs new file mode 100644 index 00000000..2ea92b11 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg b/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg Binary files differnew file mode 100644 index 00000000..64c4e8ec --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs new file mode 100644 index 00000000..6a457f04 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs new file mode 100644 index 00000000..8ec83e5c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs new file mode 100644 index 00000000..3b5f3b03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs new file mode 100644 index 00000000..c9af0727 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs new file mode 100644 index 00000000..1fa5cf2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs new file mode 100644 index 00000000..223be2f5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs @@ -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) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs new file mode 100644 index 00000000..6b54898e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs @@ -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 + { + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs new file mode 100644 index 00000000..a991f977 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + enum AccountServiceFlag + { + Administrator = 100, + SystemService = 101, + Application = 102, + BaasAccessTokenAccessor = 200 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs new file mode 100644 index 00000000..1699abfb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs @@ -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 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs new file mode 100644 index 00000000..a33e2670 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + enum NetworkServiceLicenseKind : uint + { + NoSubscription, + Subscribed + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs new file mode 100644 index 00000000..09f9d142 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs @@ -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; } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs new file mode 100644 index 00000000..e5577a94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs new file mode 100644 index 00000000..210b369c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs new file mode 100644 index 00000000..06ff4833 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs @@ -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; } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs new file mode 100644 index 00000000..72301349 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Dauth +{ + [Service("dauth:0")] // 5.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs new file mode 100644 index 00000000..34114ec9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs @@ -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 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs new file mode 100644 index 00000000..bf86aaaa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs new file mode 100644 index 00000000..dc26d80c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs new file mode 100644 index 00000000..0057eba3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs new file mode 100644 index 00000000..69967c56 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs @@ -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[]>(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs new file mode 100644 index 00000000..176bd632 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs new file mode 100644 index 00000000..6acd18cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs new file mode 100644 index 00000000..c42202b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAppletCommonFunctions : IpcService + { + public IAppletCommonFunctions() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs new file mode 100644 index 00000000..79e5b050 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IApplicationCreator : IpcService + { + public IApplicationCreator() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs new file mode 100644 index 00000000..48dd42e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs new file mode 100644 index 00000000..381267b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs new file mode 100644 index 00000000..51a112fd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDebugFunctions : IpcService + { + public IDebugFunctions() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs new file mode 100644 index 00000000..92c97d86 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs new file mode 100644 index 00000000..24eeefb9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IGlobalStateController : IpcService + { + public IGlobalStateController() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs new file mode 100644 index 00000000..c7c073ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs new file mode 100644 index 00000000..fb870c24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs new file mode 100644 index 00000000..399e778a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs new file mode 100644 index 00000000..730df5d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs new file mode 100644 index 00000000..84fc5c83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum AlbumReportOption + { + OverlayNotDisplayed, + OverlayDisplayed, + Unknown2, + Unknown3 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs new file mode 100644 index 00000000..2920c329 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs @@ -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 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs new file mode 100644 index 00000000..dfd7d7f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs new file mode 100644 index 00000000..a82ed476 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum OperationMode + { + Handheld = 0, + Docked = 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs new file mode 100644 index 00000000..e8ba9b61 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum WirelessPriorityMode + { + Default, + OptimizedForWlan, + Unknown2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs new file mode 100644 index 00000000..fb16c86e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs @@ -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(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs new file mode 100644 index 00000000..6c9197b3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs @@ -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); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs new file mode 100644 index 00000000..728a1018 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs new file mode 100644 index 00000000..ca79bac7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs @@ -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; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs new file mode 100644 index 00000000..190f1a51 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs new file mode 100644 index 00000000..4c7e264d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs new file mode 100644 index 00000000..49e342f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -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(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs new file mode 100644 index 00000000..917f6865 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs @@ -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 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs new file mode 100644 index 00000000..17a485ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs @@ -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; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs new file mode 100644 index 00000000..6c528337 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs @@ -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; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs new file mode 100644 index 00000000..fc1c11e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs @@ -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; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs new file mode 100644 index 00000000..6b9a2284 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Flags] + enum LibraryAppletMode : uint + { + AllForeground, + PartialForeground, + NoUi, + PartialForegroundWithIndirectDisplay, + AllForegroundInitiallyHidden + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs new file mode 100644 index 00000000..5ae8f459 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs new file mode 100644 index 00000000..40432074 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum LaunchParameterKind : uint + { + UserChannel = 1, + PreselectedUser, + Unknown + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs new file mode 100644 index 00000000..efc284a5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum ProgramSpecifyKind : uint + { + ExecuteProgram, + SubApplicationProgram, + RestartProgram + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs new file mode 100644 index 00000000..50e3be27 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs new file mode 100644 index 00000000..3a4c71e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs new file mode 100644 index 00000000..8c72319c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Idle +{ + [Service("idle:sys")] + class IPolicyManagerSystem : IpcService + { + public IPolicyManagerSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs new file mode 100644 index 00000000..2856e6d7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Omm +{ + [Service("omm")] + class IOperationModeManager : IpcService + { + public IOperationModeManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs new file mode 100644 index 00000000..5cafff67 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -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 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs new file mode 100644 index 00000000..a393f76b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Spsm +{ + [Service("spsm")] + class IPowerStateInterface : IpcService + { + public IPowerStateInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs new file mode 100644 index 00000000..b31ccf8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("tcap")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs new file mode 100644 index 00000000..72e39a77 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs b/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs new file mode 100644 index 00000000..9620c30a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + // NOTE: This service doesn’t 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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs new file mode 100644 index 00000000..f828cd17 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs new file mode 100644 index 00000000..9d2c7b0b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs new file mode 100644 index 00000000..af051934 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs b/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs new file mode 100644 index 00000000..d03bf6c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs @@ -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 + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs new file mode 100644 index 00000000..c4499b01 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum ResultCode + { + ModuleId = 148, + ErrorCodeShift = 9, + + Success = 0, + + InvalidParameters = (1 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs new file mode 100644 index 00000000..3ef713cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs @@ -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. + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs new file mode 100644 index 00000000..a6264236 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs @@ -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); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs new file mode 100644 index 00000000..587142c8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs @@ -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. + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs new file mode 100644 index 00000000..e8c5752e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs @@ -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+ + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs new file mode 100644 index 00000000..6d6f9643 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum PerformanceMode : uint + { + Default = 0, + Boost = 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 00000000..3e4eca0a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -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 + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs new file mode 100644 index 00000000..35a2de0c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Arp +{ + [Service("arp:r")] + class IReader : IpcService + { + public IReader(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs new file mode 100644 index 00000000..8d13f0fb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Arp +{ + [Service("arp:w")] + class IWriter : IpcService + { + public IWriter(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs new file mode 100644 index 00000000..d7686871 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs @@ -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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs new file mode 100644 index 00000000..ee85ded9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs new file mode 100644 index 00000000..a80b9402 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs @@ -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(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs new file mode 100644 index 00000000..b5073fce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs @@ -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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs new file mode 100644 index 00000000..2d342206 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs new file mode 100644 index 00000000..755caee5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs new file mode 100644 index 00000000..f2588452 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs @@ -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(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs new file mode 100644 index 00000000..329e1794 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs @@ -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(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs new file mode 100644 index 00000000..8533d3c5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs @@ -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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs new file mode 100644 index 00000000..7b289196 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs new file mode 100644 index 00000000..7c5d8c4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs new file mode 100644 index 00000000..724a1e9e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs new file mode 100644 index 00000000..e7a75121 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs @@ -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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs new file mode 100644 index 00000000..55bf29ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs @@ -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(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs new file mode 100644 index 00000000..5b682bf8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs @@ -0,0 +1,122 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioRenderer : IAudioRenderer + { + private AudioRenderSystem _impl; + + public AudioRenderer(AudioRenderSystem impl) + { + _impl = impl; + } + + public ResultCode ExecuteAudioRendererRendering() + { + return (ResultCode)_impl.ExecuteAudioRendererRendering(); + } + + public uint GetMixBufferCount() + { + return _impl.GetMixBufferCount(); + } + + public uint GetRenderingTimeLimit() + { + return _impl.GetRenderingTimeLimit(); + } + + public uint GetSampleCount() + { + return _impl.GetSampleCount(); + } + + public uint GetSampleRate() + { + return _impl.GetSampleRate(); + } + + public int GetState() + { + if (_impl.IsActive()) + { + return 0; + } + + return 1; + } + + public ResultCode QuerySystemEvent(out KEvent systemEvent) + { + ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent); + + if (resultCode == ResultCode.Success) + { + if (outEvent is AudioKernelEvent) + { + systemEvent = ((AudioKernelEvent)outEvent).Event; + } + else + { + throw new NotImplementedException(); + } + } + else + { + systemEvent = null; + } + + return resultCode; + } + + public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input) + { + return (ResultCode)_impl.Update(output, performanceOutput, input); + } + + public void SetRenderingTimeLimit(uint percent) + { + _impl.SetRenderingTimeLimitPercent(percent); + } + + public ResultCode Start() + { + _impl.Start(); + + return ResultCode.Success; + } + + public ResultCode Stop() + { + _impl.Stop(); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + + public void SetVoiceDropParameter(float voiceDropParameter) + { + _impl.SetVoiceDropParameter(voiceDropParameter); + } + + public float GetVoiceDropParameter() + { + return _impl.GetVoiceDropParameter(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs new file mode 100644 index 00000000..a137c413 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs @@ -0,0 +1,217 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + class AudioRendererServer : DisposableIpcService + { + private IAudioRenderer _impl; + + public AudioRendererServer(IAudioRenderer impl) + { + _impl = impl; + } + + [CommandCmif(0)] + // GetSampleRate() -> u32 + public ResultCode GetSampleRate(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetSampleRate()); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSampleCount() -> u32 + public ResultCode GetSampleCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetSampleCount()); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetMixBufferCount() -> u32 + public ResultCode GetMixBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetMixBufferCount()); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetState()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input) + // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput) + public ResultCode RequestUpdate(ServiceCtx context) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position; + ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size; + + ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray(); + + using (IMemoryOwner<byte> outputOwner = ByteMemoryPool.Shared.RentCleared(outputSize)) + using (IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Shared.RentCleared(performanceOutputSize)) + { + Memory<byte> output = outputOwner.Memory; + Memory<byte> performanceOutput = performanceOutputOwner.Memory; + + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); + + if (result == ResultCode.Success) + { + context.Memory.Write(outputPosition, output.Span); + context.Memory.Write(performanceOutputPosition, performanceOutput.Span); + } + else + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}"); + } + + return result; + } + } + + [CommandCmif(5)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [CommandCmif(6)] + // Stop() + public ResultCode Stop(ServiceCtx context) + { + return _impl.Stop(); + } + + [CommandCmif(7)] + // QuerySystemEvent() -> handle<copy, event> + public ResultCode QuerySystemEvent(ServiceCtx context) + { + ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent); + + if (result == ResultCode.Success) + { + if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + } + + return result; + } + + [CommandCmif(8)] + // SetAudioRendererRenderingTimeLimit(u32 limit) + public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context) + { + uint limit = context.RequestData.ReadUInt32(); + + _impl.SetRenderingTimeLimit(limit); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // GetAudioRendererRenderingTimeLimit() -> u32 limit + public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context) + { + uint limit = _impl.GetRenderingTimeLimit(); + + context.ResponseData.Write(limit); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 3.0.0+ + // RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input) + // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput) + public ResultCode RequestUpdateAuto(ServiceCtx context) + { + (ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21(); + (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0); + (ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1); + + ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray(); + + Memory<byte> output = new byte[outputSize]; + Memory<byte> performanceOutput = new byte[performanceOutputSize]; + + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); + + if (result == ResultCode.Success) + { + context.Memory.Write(outputPosition, output.Span); + context.Memory.Write(performanceOutputPosition, performanceOutput.Span); + } + + return result; + } + + [CommandCmif(11)] // 3.0.0+ + // ExecuteAudioRendererRendering() + public ResultCode ExecuteAudioRendererRendering(ServiceCtx context) + { + return _impl.ExecuteAudioRendererRendering(); + } + + [CommandCmif(12)] // 15.0.0+ + // SetVoiceDropParameter(f32 voiceDropParameter) + public ResultCode SetVoiceDropParameter(ServiceCtx context) + { + float voiceDropParameter = context.RequestData.ReadSingle(); + + _impl.SetVoiceDropParameter(voiceDropParameter); + + return ResultCode.Success; + } + + [CommandCmif(13)] // 15.0.0+ + // GetVoiceDropParameter() -> f32 voiceDropParameter + public ResultCode GetVoiceDropParameter(ServiceCtx context) + { + float voiceDropParameter = _impl.GetVoiceDropParameter(); + + context.ResponseData.Write(voiceDropParameter); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _impl.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs new file mode 100644 index 00000000..1918a977 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs @@ -0,0 +1,18 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + interface IAudioDevice + { + string[] ListAudioDeviceName(); + ResultCode SetAudioDeviceOutputVolume(string name, float volume); + ResultCode GetAudioDeviceOutputVolume(string name, out float volume); + string GetActiveAudioDeviceName(); + KEvent QueryAudioDeviceSystemEvent(); + uint GetActiveChannelCount(); + KEvent QueryAudioDeviceInputEvent(); + KEvent QueryAudioDeviceOutputEvent(); + string GetActiveAudioOutputDeviceName(); + string[] ListAudioOutputDeviceName(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs new file mode 100644 index 00000000..404bf4c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs @@ -0,0 +1,22 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer +{ + interface IAudioRenderer : IDisposable + { + uint GetSampleRate(); + uint GetSampleCount(); + uint GetMixBufferCount(); + int GetState(); + ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input); + ResultCode Start(); + ResultCode Stop(); + ResultCode QuerySystemEvent(out KEvent systemEvent); + void SetRenderingTimeLimit(uint percent); + uint GetRenderingTimeLimit(); + ResultCode ExecuteAudioRendererRendering(); + void SetVoiceDropParameter(float voiceDropParameter); + float GetVoiceDropParameter(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs new file mode 100644 index 00000000..40e71a43 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; + +using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + class AudioRendererManager : IAudioRendererManager + { + private AudioRendererManagerImpl _impl; + private VirtualDeviceSessionRegistry _registry; + + public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry) + { + _impl = impl; + _registry = registry; + } + + public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId) + { + outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision); + + return ResultCode.Success; + } + + public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter); + } + + public ResultCode OpenAudioRenderer( + ServiceCtx context, + out IAudioRenderer obj, + ref AudioRendererConfiguration parameter, + ulong workBufferSize, + ulong appletResourceUserId, + KTransferMemory workBufferTransferMemory, + uint processHandle) + { + var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; + + ResultCode result = (ResultCode)_impl.OpenAudioRenderer( + out AudioRenderSystem renderer, + memoryManager, + ref parameter, + appletResourceUserId, + workBufferTransferMemory.Address, + workBufferTransferMemory.Size, + processHandle, + context.Device.Configuration.AudioVolume); + + if (result == ResultCode.Success) + { + obj = new AudioRenderer.AudioRenderer(renderer); + } + else + { + obj = null; + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs new file mode 100644 index 00000000..80b54e8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs @@ -0,0 +1,116 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:u")] + class AudioRendererManagerServer : IpcService + { + private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); + + private IAudioRendererManager _impl; + + public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { } + + public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer) + { + _impl = impl; + } + + [CommandCmif(0)] + // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle) + // -> object<nn::audio::detail::IAudioRenderer> + public ResultCode OpenAudioRenderer(ServiceCtx context) + { + AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>(); + ulong workBufferSize = context.RequestData.ReadUInt64(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle); + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1]; + + ResultCode result = _impl.OpenAudioRenderer( + context, + out IAudioRenderer renderer, + ref parameter, + workBufferSize, + appletResourceUserId, + workBufferTransferMemory, + processHandle); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioRendererServer(renderer)); + } + + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle); + context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle); + + return result; + } + + [CommandCmif(1)] + // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize + public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context) + { + AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>(); + + if (BehaviourContext.CheckValidRevision(parameter.Revision)) + { + ulong size = _impl.GetWorkBufferSize(ref parameter); + + context.ResponseData.Write(size); + + Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}."); + + return ResultCode.Success; + } + else + { + context.ResponseData.Write(0L); + + Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!"); + + return ResultCode.UnsupportedRevision; + } + } + + [CommandCmif(2)] + // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice> + public ResultCode GetAudioDeviceService(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioDeviceServer(device)); + } + + return result; + } + + [CommandCmif(4)] // 4.0.0+ + // GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice> + public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context) + { + int revision = context.RequestData.ReadInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId); + + if (result == ResultCode.Success) + { + MakeObject(context, new AudioDeviceServer(device)); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs new file mode 100644 index 00000000..b77fc4b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs @@ -0,0 +1,27 @@ +using Concentus.Structs; + +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + class Decoder : IDecoder + { + private readonly OpusDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount => _decoder.NumChannels; + + public Decoder(int sampleRate, int channelsCount) + { + _decoder = new OpusDecoder(sampleRate, channelsCount); + } + + public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + { + return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); + } + + public void ResetState() + { + _decoder.ResetState(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs new file mode 100644 index 00000000..944541cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs @@ -0,0 +1,92 @@ +using Concentus; +using Concentus.Enums; +using Concentus.Structs; +using Ryujinx.HLE.HOS.Services.Audio.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + static class DecoderCommon + { + private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet) + { + int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); + + numSamples = result; + + if (result == OpusError.OPUS_INVALID_PACKET) + { + return ResultCode.OpusInvalidInput; + } + else if (result == OpusError.OPUS_BAD_ARG) + { + return ResultCode.OpusInvalidInput; + } + + return ResultCode.Success; + } + + public static ResultCode DecodeInterleaved( + this IDecoder decoder, + bool reset, + ReadOnlySpan<byte> input, + out short[] outPcmData, + ulong outputSize, + out uint outConsumed, + out int outSamples) + { + outPcmData = null; + outConsumed = 0; + outSamples = 0; + + int streamSize = input.Length; + + if (streamSize < Unsafe.SizeOf<OpusPacketHeader>()) + { + return ResultCode.OpusInvalidInput; + } + + OpusPacketHeader header = OpusPacketHeader.FromSpan(input); + int headerSize = Unsafe.SizeOf<OpusPacketHeader>(); + uint totalSize = header.length + (uint)headerSize; + + if (totalSize > streamSize) + { + return ResultCode.OpusInvalidInput; + } + + byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray(); + + ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData); + + if (result == ResultCode.Success) + { + if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize) + { + return ResultCode.OpusInvalidInput; + } + + outPcmData = new short[numSamples * decoder.ChannelsCount]; + + if (reset) + { + decoder.ResetState(); + } + + try + { + outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); + outConsumed = totalSize; + } + catch (OpusException) + { + // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases... + return ResultCode.OpusInvalidInput; + } + } + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs new file mode 100644 index 00000000..9047c266 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + interface IDecoder + { + int SampleRate { get; } + int ChannelsCount { get; } + + int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); + void ResetState(); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs new file mode 100644 index 00000000..e94b31ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs @@ -0,0 +1,116 @@ +using Ryujinx.HLE.HOS.Services.Audio.Types; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + class IHardwareOpusDecoder : IpcService + { + private readonly IDecoder _decoder; + private readonly OpusDecoderFlags _flags; + + public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags) + { + _decoder = new Decoder(sampleRate, channelsCount); + _flags = flags; + } + + public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping) + { + _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + _flags = flags; + } + + [CommandCmif(0)] + // DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>) + public ResultCode DecodeInterleavedOld(ServiceCtx context) + { + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false); + } + + [CommandCmif(2)] + // DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>) + public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context) + { + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false); + } + + [CommandCmif(4)] // 6.0.0+ + // DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context) + { + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true); + } + + [CommandCmif(5)] // 6.0.0+ + // DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context) + { + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true); + } + + [CommandCmif(6)] // 6.0.0+ + // DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context) + { + bool reset = context.RequestData.ReadBoolean(); + + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true); + } + + [CommandCmif(7)] // 6.0.0+ + // DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context) + { + bool reset = context.RequestData.ReadBoolean(); + + return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true); + } + + [CommandCmif(8)] // 7.0.0+ + // DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleaved(ServiceCtx context) + { + bool reset = context.RequestData.ReadBoolean(); + + return DecodeInterleavedInternal(context, _flags, reset, withPerf: true); + } + + [CommandCmif(9)] // 7.0.0+ + // DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>) + public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context) + { + bool reset = context.RequestData.ReadBoolean(); + + return DecodeInterleavedInternal(context, _flags, reset, withPerf: true); + } + + private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf) + { + ulong inPosition = context.Request.SendBuff[0].Position; + ulong inSize = context.Request.SendBuff[0].Size; + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize); + + ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples); + + if (result == ResultCode.Success) + { + context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan())); + + context.ResponseData.Write(outConsumed); + context.ResponseData.Write(outSamples); + + if (withPerf) + { + // This is the time the DSP took to process the request, TODO: fill this. + context.ResponseData.Write(0UL); + } + } + + return result; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs new file mode 100644 index 00000000..23721d3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs @@ -0,0 +1,28 @@ +using Concentus.Structs; + +namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager +{ + class MultiSampleDecoder : IDecoder + { + private readonly OpusMSDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount { get; } + + public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) + { + ChannelsCount = channelsCount; + _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + { + return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); + } + + public void ResetState() + { + _decoder.ResetState(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs new file mode 100644 index 00000000..1bd2e31d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audctl")] + class IAudioController : IpcService + { + public IAudioController(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs new file mode 100644 index 00000000..9bbe5b0e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs @@ -0,0 +1,12 @@ +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioIn; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + interface IAudioInManager + { + public string[] ListAudioIns(bool filtered); + + public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs new file mode 100644 index 00000000..37d9a8fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:a")] + class IAudioInManagerForApplet : IpcService + { + public IAudioInManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs new file mode 100644 index 00000000..1a497efb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:d")] + class IAudioInManagerForDebugger : IpcService + { + public IAudioInManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs new file mode 100644 index 00000000..70e60d2e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -0,0 +1,12 @@ +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioOut; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + interface IAudioOutManager + { + public string[] ListAudioOuts(); + + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs new file mode 100644 index 00000000..4b41b0cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:a")] + class IAudioOutManagerForApplet : IpcService + { + public IAudioOutManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs new file mode 100644 index 00000000..41cde972 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:d")] + class IAudioOutManagerForDebugger : IpcService + { + public IAudioOutManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs new file mode 100644 index 00000000..642e2525 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + interface IAudioRendererManager + { + // TODO: Remove ServiceCtx argument + // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. + ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId); + + // TODO: Remove ServiceCtx argument + // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend. + ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle); + + ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs new file mode 100644 index 00000000..ca5768cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:a")] + class IAudioRendererManagerForApplet : IpcService + { + public IAudioRendererManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs new file mode 100644 index 00000000..a970ae45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:d")] + class IAudioRendererManagerForDebugger : IpcService + { + public IAudioRendererManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs new file mode 100644 index 00000000..59e3ad09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("auddev")] // 6.0.0+ + class IAudioSnoopManager : IpcService + { + public IAudioSnoopManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs new file mode 100644 index 00000000..01435008 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:u")] + class IFinalOutputRecorderManager : IpcService + { + public IFinalOutputRecorderManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs new file mode 100644 index 00000000..d8fd270d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:a")] + class IFinalOutputRecorderManagerForApplet : IpcService + { + public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs new file mode 100644 index 00000000..a8ec51ee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:d")] + class IFinalOutputRecorderManagerForDebugger : IpcService + { + public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs new file mode 100644 index 00000000..8df8a38c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs @@ -0,0 +1,205 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager; +using Ryujinx.HLE.HOS.Services.Audio.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("hwopus")] + class IHardwareOpusDecoderManager : IpcService + { + public IHardwareOpusDecoderManager(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(bytes<8, 4>, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder> + public ResultCode Initialize(ServiceCtx context) + { + int sampleRate = context.RequestData.ReadInt32(); + int channelsCount = context.RequestData.ReadInt32(); + + MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None)); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetWorkBufferSize(bytes<8, 4>) -> u32 + public ResultCode GetWorkBufferSize(ServiceCtx context) + { + int sampleRate = context.RequestData.ReadInt32(); + int channelsCount = context.RequestData.ReadInt32(); + + int opusDecoderSize = GetOpusDecoderSize(channelsCount); + + int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64); + int totalSize = opusDecoderSize + 1536 + frameSize; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandCmif(2)] // 3.0.0+ + // InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder> + public ResultCode InitializeForMultiStream(ServiceCtx context) + { + ulong parametersAddress = context.Request.PtrBuff[0].Position; + + OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress); + + MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None)); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(3)] // 3.0.0+ + // GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32 + public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context) + { + ulong parametersAddress = context.Request.PtrBuff[0].Position; + + OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress); + + int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams); + + int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64); + int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64); + int totalSize = opusDecoderSize + streamSize + frameSize; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandCmif(4)] // 12.0.0+ + // InitializeEx(OpusParametersEx, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder> + public ResultCode InitializeEx(ServiceCtx context) + { + OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>(); + + // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result. + MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags)); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 12.0.0+ + // GetWorkBufferSizeEx(OpusParametersEx) -> u32 + public ResultCode GetWorkBufferSizeEx(ServiceCtx context) + { + OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>(); + + int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount); + + int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64); + int totalSize = opusDecoderSize + 1536 + frameSize; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandCmif(6)] // 12.0.0+ + // InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder> + public ResultCode InitializeForMultiStreamEx(ServiceCtx context) + { + ulong parametersAddress = context.Request.PtrBuff[0].Position; + + OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress); + + byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.AsSpan()).ToArray(); + + // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result. + MakeObject(context, new IHardwareOpusDecoder( + parameters.SampleRate, + parameters.ChannelsCount, + parameters.NumberOfStreams, + parameters.NumberOfStereoStreams, + parameters.Flags, + mappings)); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(7)] // 12.0.0+ + // GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32 + public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context) + { + ulong parametersAddress = context.Request.PtrBuff[0].Position; + + OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress); + + int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams); + + int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64); + int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64); + int totalSize = opusDecoderSize + streamSize + frameSize; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + if (streams < 1 || coupledStreams > streams || coupledStreams < 0) + { + return 0; + } + + int coupledSize = GetOpusDecoderSize(2); + int monoSize = GetOpusDecoderSize(1); + + return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) + + Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c; + } + + private static int Align4(int value) + { + return BitUtils.AlignUp(value, 4); + } + + private static int GetOpusDecoderSize(int channelsCount) + { + const int SilkDecoderSize = 0x2160; + + if (channelsCount < 1 || channelsCount > 2) + { + return 0; + } + + int celtDecoderSize = GetCeltDecoderSize(channelsCount); + int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c; + + return opusDecoderSize + SilkDecoderSize + celtDecoderSize; + } + + private static int GetOpusDecoderAllocSize(int channelsCount) + { + return (channelsCount * 0x800 + 0x4803) & -0x800; + } + + private static int GetCeltDecoderSize(int channelsCount) + { + const int DecodeBufferSize = 0x2030; + const int Overlap = 120; + const int EBandsCount = 21; + + return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs new file mode 100644 index 00000000..fd2091c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId, + OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId, + TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId, + InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId, + InvalidOperation = (513 << ErrorCodeShift) | ModuleId, + InvalidHandle = (1536 << ErrorCodeShift) | ModuleId, + OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs new file mode 100644 index 00000000..e49c294c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [Flags] + enum OpusDecoderFlags : uint + { + None, + LargeFrameSize = 1 << 0, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs new file mode 100644 index 00000000..fd63a4f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x110)] + struct OpusMultiStreamParameters + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public Array64<uint> ChannelMappings; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs new file mode 100644 index 00000000..1315c734 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x118)] + struct OpusMultiStreamParametersEx + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public OpusDecoderFlags Flags; + + Array4<byte> Padding1; + + public Array64<uint> ChannelMappings; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs new file mode 100644 index 00000000..5ae0eb1e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs @@ -0,0 +1,23 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct OpusPacketHeader + { + public uint length; + public uint finalRange; + + public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data) + { + OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0]; + + header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length; + header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange; + + return header; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs new file mode 100644 index 00000000..f088ed01 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct OpusParametersEx + { + public int SampleRate; + public int ChannelsCount; + public OpusDecoderFlags Flags; + + Array4<byte> Padding1; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs new file mode 100644 index 00000000..1437a8e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs @@ -0,0 +1,85 @@ +using LibHac; +using LibHac.Common; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator; + +namespace Ryujinx.HLE.HOS.Services.Bcat +{ + [Service("bcat:a", "bcat:a")] + [Service("bcat:m", "bcat:m")] + [Service("bcat:u", "bcat:u")] + [Service("bcat:s", "bcat:s")] + class IServiceCreator : DisposableIpcService + { + private SharedRef<LibHac.Bcat.Impl.Ipc.IServiceCreator> _base; + + public IServiceCreator(ServiceCtx context, string serviceName) + { + var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient; + applicationClient.Sm.GetService(ref _base, serviceName).ThrowIfFailure(); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _base.Destroy(); + } + } + + [CommandCmif(0)] + // CreateBcatService(pid) -> object<nn::bcat::detail::ipc::IBcatService> + public ResultCode CreateBcatService(ServiceCtx context) + { + // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. + // Add an instance of nn::bcat::detail::service::core::PassphraseManager. + // Add an instance of nn::bcat::detail::service::ServiceMemoryManager. + // Add an instance of nn::bcat::detail::service::core::TaskManager who load "bcat-sys:/" system save data and open "dc/task.bin". + // If the file don't exist, create a new one (size of 0x800) and write 2 empty struct with a size of 0x400. + + MakeObject(context, new IBcatService(ApplicationLaunchProperty.GetByPid(context))); + + // NOTE: If the IBcatService is null this error is returned, Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [CommandCmif(1)] + // CreateDeliveryCacheStorageService(pid) -> object<nn::bcat::detail::ipc::IDeliveryCacheStorageService> + public ResultCode CreateDeliveryCacheStorageService(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + using var serv = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>(); + + Result rc = _base.Get.CreateDeliveryCacheStorageService(ref serv.Ref, pid); + + if (rc.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheStorageService(context, ref serv.Ref)); + } + + return (ResultCode)rc.Value; + } + + [CommandCmif(2)] + // CreateDeliveryCacheStorageServiceWithApplicationId(nn::ApplicationId) -> object<nn::bcat::detail::ipc::IDeliveryCacheStorageService> + public ResultCode CreateDeliveryCacheStorageServiceWithApplicationId(ServiceCtx context) + { + ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>(); + + using var service = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>(); + + Result rc = _base.Get.CreateDeliveryCacheStorageServiceWithApplicationId(ref service.Ref, applicationId); + + if (rc.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheStorageService(context, ref service.Ref)); + } + + return (ResultCode)rc.Value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs new file mode 100644 index 00000000..7f1b313e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.HLE.HOS.Services.Bcat +{ + enum ResultCode + { + ModuleId = 122, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (1 << ErrorCodeShift) | ModuleId, + NotFound = (2 << ErrorCodeShift) | ModuleId, + TargetLocked = (3 << ErrorCodeShift) | ModuleId, + TargetAlreadyMounted = (4 << ErrorCodeShift) | ModuleId, + TargetNotMounted = (5 << ErrorCodeShift) | ModuleId, + AlreadyOpen = (6 << ErrorCodeShift) | ModuleId, + NotOpen = (7 << ErrorCodeShift) | ModuleId, + InternetRequestDenied = (8 << ErrorCodeShift) | ModuleId, + ServiceOpenLimitReached = (9 << ErrorCodeShift) | ModuleId, + SaveDataNotFound = (10 << ErrorCodeShift) | ModuleId, + NetworkServiceAccountNotAvailable = (31 << ErrorCodeShift) | ModuleId, + PassphrasePathNotFound = (80 << ErrorCodeShift) | ModuleId, + DataVerificationFailed = (81 << ErrorCodeShift) | ModuleId, + PermissionDenied = (90 << ErrorCodeShift) | ModuleId, + AllocationFailed = (91 << ErrorCodeShift) | ModuleId, + InvalidOperation = (98 << ErrorCodeShift) | ModuleId, + InvalidDeliveryCacheStorageFile = (204 << ErrorCodeShift) | ModuleId, + StorageOpenLimitReached = (205 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs new file mode 100644 index 00000000..fb11ceda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs @@ -0,0 +1,18 @@ +using Ryujinx.HLE.HOS.Services.Arp; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IBcatService : IpcService + { + public IBcatService(ApplicationLaunchProperty applicationLaunchProperty) { } + + [CommandCmif(10100)] + // RequestSyncDeliveryCache() -> object<nn::bcat::detail::ipc::IDeliveryCacheProgressService> + public ResultCode RequestSyncDeliveryCache(ServiceCtx context) + { + MakeObject(context, new IDeliveryCacheProgressService(context)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs new file mode 100644 index 00000000..57544977 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs @@ -0,0 +1,65 @@ +using LibHac; +using LibHac.Bcat; +using LibHac.Common; +using Ryujinx.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheDirectoryService : DisposableIpcService + { + private SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService> _base; + + public IDeliveryCacheDirectoryService(ref SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService> baseService) + { + _base = SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService>.CreateMove(ref baseService); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _base.Destroy(); + } + } + + [CommandCmif(0)] + // Open(nn::bcat::DirectoryName) + public ResultCode Open(ServiceCtx context) + { + DirectoryName directoryName = context.RequestData.ReadStruct<DirectoryName>(); + + Result result = _base.Get.Open(ref directoryName); + + return (ResultCode)result.Value; + } + + [CommandCmif(1)] + // Read() -> (u32, buffer<nn::bcat::DeliveryCacheDirectoryEntry, 6>) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _base.Get.Read(out int entriesRead, MemoryMarshal.Cast<byte, DeliveryCacheDirectoryEntry>(region.Memory.Span)); + + context.ResponseData.Write(entriesRead); + + return (ResultCode)result.Value; + } + } + + [CommandCmif(2)] + // GetCount() -> u32 + public ResultCode GetCount(ServiceCtx context) + { + Result result = _base.Get.GetCount(out int count); + + context.ResponseData.Write(count); + + return (ResultCode)result.Value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs new file mode 100644 index 00000000..5a9110e6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs @@ -0,0 +1,78 @@ +using LibHac; +using LibHac.Bcat; +using LibHac.Common; +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheFileService : DisposableIpcService + { + private SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService> _base; + + public IDeliveryCacheFileService(ref SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService> baseService) + { + _base = SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService>.CreateMove(ref baseService); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _base.Destroy(); + } + } + + [CommandCmif(0)] + // Open(nn::bcat::DirectoryName, nn::bcat::FileName) + public ResultCode Open(ServiceCtx context) + { + DirectoryName directoryName = context.RequestData.ReadStruct<DirectoryName>(); + FileName fileName = context.RequestData.ReadStruct<FileName>(); + + Result result = _base.Get.Open(ref directoryName, ref fileName); + + return (ResultCode)result.Value; + } + + [CommandCmif(1)] + // Read(u64) -> (u64, buffer<bytes, 6>) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + long offset = context.RequestData.ReadInt64(); + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _base.Get.Read(out long bytesRead, offset, region.Memory.Span); + + context.ResponseData.Write(bytesRead); + + return (ResultCode)result.Value; + } + } + + [CommandCmif(2)] + // GetSize() -> u64 + public ResultCode GetSize(ServiceCtx context) + { + Result result = _base.Get.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [CommandCmif(3)] + // GetDigest() -> nn::bcat::Digest + public ResultCode GetDigest(ServiceCtx context) + { + Result result = _base.Get.GetDigest(out Digest digest); + + context.ResponseData.WriteStruct(digest); + + return (ResultCode)result.Value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs new file mode 100644 index 00000000..1555f170 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheProgressService : IpcService + { + private KEvent _event; + private int _eventHandle; + + public IDeliveryCacheProgressService(ServiceCtx context) + { + _event = new KEvent(context.Device.System.KernelContext); + } + + [CommandCmif(0)] + // GetEvent() -> handle<copy> + public ResultCode GetEvent(ServiceCtx context) + { + if (_eventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out _eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_eventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceBcat); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetImpl() -> buffer<nn::bcat::detail::DeliveryCacheProgressImpl, 0x1a> + public ResultCode GetImpl(ServiceCtx context) + { + DeliveryCacheProgressImpl deliveryCacheProgress = new DeliveryCacheProgressImpl + { + State = DeliveryCacheProgressImpl.Status.Done, + Result = 0 + }; + + ulong dcpSize = WriteDeliveryCacheProgressImpl(context, context.Request.RecvListBuff[0], deliveryCacheProgress); + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(dcpSize); + + Logger.Stub?.PrintStub(LogClass.ServiceBcat); + + return ResultCode.Success; + } + + private ulong WriteDeliveryCacheProgressImpl(ServiceCtx context, IpcRecvListBuffDesc ipcDesc, DeliveryCacheProgressImpl deliveryCacheProgress) + { + return MemoryHelper.Write(context.Memory, ipcDesc.Position, deliveryCacheProgress); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs new file mode 100644 index 00000000..be77226c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs @@ -0,0 +1,74 @@ +using LibHac; +using LibHac.Bcat; +using LibHac.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheStorageService : DisposableIpcService + { + private SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService> _base; + + public IDeliveryCacheStorageService(ServiceCtx context, ref SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService> baseService) + { + _base = SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>.CreateMove(ref baseService); + } + + [CommandCmif(0)] + // CreateFileService() -> object<nn::bcat::detail::ipc::IDeliveryCacheFileService> + public ResultCode CreateFileService(ServiceCtx context) + { + using var service = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService>(); + + Result result = _base.Get.CreateFileService(ref service.Ref); + + if (result.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheFileService(ref service.Ref)); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(1)] + // CreateDirectoryService() -> object<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService> + public ResultCode CreateDirectoryService(ServiceCtx context) + { + using var service = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService>(); + + Result result = _base.Get.CreateDirectoryService(ref service.Ref); + + if (result.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheDirectoryService(ref service.Ref)); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(10)] + // EnumerateDeliveryCacheDirectory() -> (u32, buffer<nn::bcat::DirectoryName, 6>) + public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _base.Get.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast<byte, DirectoryName>(region.Memory.Span)); + + context.ResponseData.Write(count); + + return (ResultCode)result.Value; + } + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _base.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs new file mode 100644 index 00000000..fb9a67be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200)] + public struct DeliveryCacheProgressImpl + { + public enum Status + { + // TODO: determine other values + Done = 9 + } + + public Status State; + public uint Result; + // TODO: reverse the rest of the structure + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs b/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs new file mode 100644 index 00000000..4926d4d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:sc")] + class IStateControlService : IpcService + { + public IStateControlService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs b/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs new file mode 100644 index 00000000..a032c380 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:t")] + class ITaskService : IpcService + { + public ITaskService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs new file mode 100644 index 00000000..81f4a7d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver +{ + static class BluetoothEventManager + { + public static KEvent InitializeBleDebugEvent; + public static int InitializeBleDebugEventHandle; + + public static KEvent UnknownBleDebugEvent; + public static int UnknownBleDebugEventHandle; + + public static KEvent RegisterBleDebugEvent; + public static int RegisterBleDebugEventHandle; + + public static KEvent InitializeBleEvent; + public static int InitializeBleEventHandle; + + public static KEvent UnknownBleEvent; + public static int UnknownBleEventHandle; + + public static KEvent RegisterBleEvent; + public static int RegisterBleEventHandle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs new file mode 100644 index 00000000..feff5a73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs @@ -0,0 +1,103 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("btdrv")] + class IBluetoothDriver : IpcService + { +#pragma warning disable CS0414 + private string _unknownLowEnergy; +#pragma warning restore CS0414 + + public IBluetoothDriver(ServiceCtx context) { } + + [CommandCmif(46)] + // InitializeBluetoothLe() -> handle<copy> + public ResultCode InitializeBluetoothLe(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + int initializeEventHandle; + + if ((bool)debugMode) + { + if (BluetoothEventManager.InitializeBleDebugEventHandle == 0) + { + BluetoothEventManager.InitializeBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleDebugEvent.ReadableEvent, out BluetoothEventManager.InitializeBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleDebugEventHandle == 0) + { + BluetoothEventManager.UnknownBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleDebugEvent.ReadableEvent, out BluetoothEventManager.UnknownBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleDebugEventHandle == 0) + { + BluetoothEventManager.RegisterBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleDebugEvent.ReadableEvent, out BluetoothEventManager.RegisterBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + initializeEventHandle = BluetoothEventManager.InitializeBleDebugEventHandle; + } + else + { + _unknownLowEnergy = "low_energy"; + + if (BluetoothEventManager.InitializeBleEventHandle == 0) + { + BluetoothEventManager.InitializeBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleEvent.ReadableEvent, out BluetoothEventManager.InitializeBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleEventHandle == 0) + { + BluetoothEventManager.UnknownBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleEvent.ReadableEvent, out BluetoothEventManager.UnknownBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleEventHandle == 0) + { + BluetoothEventManager.RegisterBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleEvent.ReadableEvent, out BluetoothEventManager.RegisterBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + initializeEventHandle = BluetoothEventManager.InitializeBleEventHandle; + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(initializeEventHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs new file mode 100644 index 00000000..1a5e25a4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("bt")] + class IBluetoothUser : IpcService + { + public IBluetoothUser(ServiceCtx context) { } + + [CommandCmif(9)] + // RegisterBleEvent(pid) -> handle<copy> + public ResultCode RegisterBleEvent(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleDebugEventHandle); + } + else + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleEventHandle); + } + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs new file mode 100644 index 00000000..3c9938ad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser +{ + class IBtmUserCore : IpcService + { + public KEvent _bleScanEvent; + public int _bleScanEventHandle; + + public KEvent _bleConnectionEvent; + public int _bleConnectionEventHandle; + + public KEvent _bleServiceDiscoveryEvent; + public int _bleServiceDiscoveryEventHandle; + + public KEvent _bleMtuConfigEvent; + public int _bleMtuConfigEventHandle; + + public IBtmUserCore() { } + + [CommandCmif(0)] // 5.0.0+ + // AcquireBleScanEvent() -> (byte<1>, handle<copy>) + public ResultCode AcquireBleScanEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleScanEventHandle == 0) + { + _bleScanEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleScanEvent.ReadableEvent, out _bleScanEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleScanEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(17)] // 5.0.0+ + // AcquireBleConnectionEvent() -> (byte<1>, handle<copy>) + public ResultCode AcquireBleConnectionEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleConnectionEventHandle == 0) + { + _bleConnectionEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleConnectionEvent.ReadableEvent, out _bleConnectionEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleConnectionEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(26)] // 5.0.0+ + // AcquireBleServiceDiscoveryEvent() -> (byte<1>, handle<copy>) + public ResultCode AcquireBleServiceDiscoveryEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleServiceDiscoveryEventHandle == 0) + { + _bleServiceDiscoveryEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleServiceDiscoveryEvent.ReadableEvent, out _bleServiceDiscoveryEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleServiceDiscoveryEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(33)] // 5.0.0+ + // AcquireBleMtuConfigEvent() -> (byte<1>, handle<copy>) + public ResultCode AcquireBleMtuConfigEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleMtuConfigEventHandle == 0) + { + _bleMtuConfigEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleMtuConfigEvent.ReadableEvent, out _bleMtuConfigEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleMtuConfigEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs new file mode 100644 index 00000000..48a273a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm")] + class IBtm : IpcService + { + public IBtm(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs new file mode 100644 index 00000000..259698af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:dbg")] + class IBtmDebug : IpcService + { + public IBtmDebug(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs new file mode 100644 index 00000000..c4210b78 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:sys")] + class IBtmSystem : IpcService + { + public IBtmSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs new file mode 100644 index 00000000..b00f0a2f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:u")] // 5.0.0+ + class IBtmUser : IpcService + { + public IBtmUser(ServiceCtx context) { } + + [CommandCmif(0)] // 5.0.0+ + // GetCore() -> object<nn::btm::IBtmUserCore> + public ResultCode GetCore(ServiceCtx context) + { + MakeObject(context, new IBtmUserCore()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs new file mode 100644 index 00000000..0ad2c485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + enum ResultCode + { + ModuleId = 143, + ErrorCodeShift = 9, + + Success = 0 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs new file mode 100644 index 00000000..6320fe28 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs @@ -0,0 +1,134 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Caps.Types; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + class CaptureManager + { + private string _sdCardPath; + + private uint _shimLibraryVersion; + + public CaptureManager(Switch device) + { + _sdCardPath = device.FileSystem.GetSdCardPath(); + } + + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + ulong shimLibraryVersion = context.RequestData.ReadUInt64(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + + ResultCode resultCode = ResultCode.OutOfRange; + + if (shimLibraryVersion != 0) + { + if (_shimLibraryVersion == shimLibraryVersion) + { + resultCode = ResultCode.Success; + } + else if (_shimLibraryVersion != 0) + { + resultCode = ResultCode.ShimLibraryVersionAlreadySet; + } + else if (shimLibraryVersion == 1) + { + resultCode = ResultCode.Success; + + _shimLibraryVersion = 1; + } + } + + return resultCode; + } + + public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry) + { + applicationAlbumEntry = default; + + if (screenshotData.Length == 0) + { + return ResultCode.NullInputBuffer; + } + + /* + // NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now. + if (appletResourceUserId == 0) + { + return ResultCode.InvalidArgument; + } + */ + + /* + // Doesn't occur in our case. + if (applicationAlbumEntry == null) + { + return ResultCode.NullOutputBuffer; + } + */ + + if (screenshotData.Length >= 0x384000) + { + DateTime currentDateTime = DateTime.Now; + + applicationAlbumEntry = new ApplicationAlbumEntry() + { + Size = (ulong)Unsafe.SizeOf<ApplicationAlbumEntry>(), + TitleId = titleId, + AlbumFileDateTime = new AlbumFileDateTime() + { + Year = (ushort)currentDateTime.Year, + Month = (byte)currentDateTime.Month, + Day = (byte)currentDateTime.Day, + Hour = (byte)currentDateTime.Hour, + Minute = (byte)currentDateTime.Minute, + Second = (byte)currentDateTime.Second, + UniqueId = 0 + }, + AlbumStorage = AlbumStorage.Sd, + ContentType = ContentType.Screenshot, + Padding = new Array5<byte>(), + Unknown0x1f = 1 + }; + + // NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead. + string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId))).Remove(0x20); + string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00")); + string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + + // TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions. + Directory.CreateDirectory(folderPath); + + while (File.Exists(filePath)) + { + applicationAlbumEntry.AlbumFileDateTime.UniqueId++; + + filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + } + + // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. + Image.LoadPixelData<Rgba32>(screenshotData, 1280, 720).SaveAsJpegAsync(filePath); + + return ResultCode.Success; + } + + return ResultCode.NullInputBuffer; + } + + private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash) + { + string fileName = $"{currentDateTime:yyyyMMddHHmmss}{applicationAlbumEntry.AlbumFileDateTime.UniqueId:00}-{hash}.jpg"; + + return Path.Combine(folderPath, fileName); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 00000000..4071b9cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:a")] + class IAlbumAccessorService : IpcService + { + public IAlbumAccessorService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs new file mode 100644 index 00000000..af99232e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs @@ -0,0 +1,69 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:u")] + class IAlbumApplicationService : IpcService + { + public IAlbumApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(102)] + // GetAlbumFileList0AafeAruidDeprecated(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer<ApplicationAlbumFileEntry, 0x6>) + public ResultCode GetAlbumFileList0AafeAruidDeprecated(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x30. + return GetAlbumFileList(context); + } + + [CommandCmif(142)] + // GetAlbumFileList3AaeAruid(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer<ApplicationAlbumFileEntry, 0x6>) + public ResultCode GetAlbumFileList3AaeAruid(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x20. + return GetAlbumFileList(context); + } + + private ResultCode GetAlbumFileList(ServiceCtx context) + { + ResultCode resultCode = ResultCode.Success; + ulong count = 0; + + ContentType contentType = (ContentType)context.RequestData.ReadUInt16(); + ulong startTime = context.RequestData.ReadUInt64(); + ulong endTime = context.RequestData.ReadUInt64(); + + context.RequestData.ReadUInt16(); // Alignment. + + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong applicationAlbumFileEntryPosition = context.Request.ReceiveBuff[0].Position; + ulong applicationAlbumFileEntrySize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, applicationAlbumFileEntryPosition, (int)applicationAlbumFileEntrySize); + + if (contentType > ContentType.Unknown || contentType == ContentType.ExtraMovie) + { + resultCode = ResultCode.InvalidContentType; + } + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + // Service populate the buffer with a ApplicationAlbumFileEntry related to the pid. + + Logger.Stub?.PrintStub(LogClass.ServiceCaps, new { contentType, startTime, endTime, appletResourceUserId }); + + context.ResponseData.Write(count); + + return resultCode; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs new file mode 100644 index 00000000..b16a4122 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:c")] + class IAlbumControlService : IpcService + { + public IAlbumControlService(ServiceCtx context) { } + + [CommandCmif(33)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs new file mode 100644 index 00000000..a3501286 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -0,0 +1,98 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:su")] // 6.0.0+ + class IScreenShotApplicationService : IpcService + { + public IScreenShotApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(203)] + // SaveScreenShotEx0(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx0(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + ulong screenshotDataPosition = context.Request.SendBuff[0].Position; + ulong screenshotDataSize = context.Request.SendBuff[0].Size; + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(205)] // 8.0.0+ + // SaveScreenShotEx1(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer<bytes, 0x15> ApplicationData, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx1(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + ulong applicationDataPosition = context.Request.SendBuff[0].Position; + ulong applicationDataSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + // TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now). + byte[] applicationData = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(210)] + // SaveScreenShotEx2(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, buffer<bytes, 0x15> UserIdList, buffer<bytes, 0x45> ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx2(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct<ScreenShotAttribute>(); + + uint unknown = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong userIdListPosition = context.Request.SendBuff[0].Position; + ulong userIdListSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + // TODO: Parse the UserIdList. + byte[] userIdList = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs new file mode 100644 index 00000000..337fa9ee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:sc")] + class IScreenShotControlService : IpcService + { + public IScreenShotControlService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs new file mode 100644 index 00000000..03703e05 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:ss")] // 2.0.0+ + class IScreenshotService : IpcService + { + public IScreenshotService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs new file mode 100644 index 00000000..2615eeda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + enum ResultCode + { + ModuleId = 206, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (2 << ErrorCodeShift) | ModuleId, + ShimLibraryVersionAlreadySet = (7 << ErrorCodeShift) | ModuleId, + OutOfRange = (8 << ErrorCodeShift) | ModuleId, + InvalidContentType = (14 << ErrorCodeShift) | ModuleId, + NullOutputBuffer = (141 << ErrorCodeShift) | ModuleId, + NullInputBuffer = (142 << ErrorCodeShift) | ModuleId, + BlacklistedPid = (822 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs new file mode 100644 index 00000000..b9bc799c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct AlbumFileDateTime + { + public ushort Year; + public byte Month; + public byte Day; + public byte Hour; + public byte Minute; + public byte Second; + public byte UniqueId; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs new file mode 100644 index 00000000..479675d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumImageOrientation : uint + { + Degrees0, + Degrees90, + Degrees180, + Degrees270 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs new file mode 100644 index 00000000..cfe6c1e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumStorage : byte + { + Nand, + Sd + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs new file mode 100644 index 00000000..699bb418 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct ApplicationAlbumEntry + { + public ulong Size; + public ulong TitleId; + public AlbumFileDateTime AlbumFileDateTime; + public AlbumStorage AlbumStorage; + public ContentType ContentType; + public Array5<byte> Padding; + public byte Unknown0x1f; // Always 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs new file mode 100644 index 00000000..5f8bb537 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum ContentType : byte + { + Screenshot, + Movie, + ExtraMovie, + Unknown + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs new file mode 100644 index 00000000..5528379a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct ScreenShotAttribute + { + public uint Unknown0x00; // Always 0 + public AlbumImageOrientation AlbumImageOrientation; + public uint Unknown0x08; // Always 0 + public uint Unknown0x0C; // Always 1 + public Array30<byte> Unknown0x10; // Always 0 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs b/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs new file mode 100644 index 00000000..71c26786 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Cec +{ + [Service("cec-mgr")] + class ICecManager : IpcService + { + public ICecManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs b/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs new file mode 100644 index 00000000..3b3279ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CommandCmifAttribute : Attribute + { + public readonly int Id; + + public CommandCmifAttribute(int id) => Id = id; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs b/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs new file mode 100644 index 00000000..0d29f92c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CommandTipcAttribute : Attribute + { + public readonly int Id; + + public CommandTipcAttribute(int id) => Id = id; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs b/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs new file mode 100644 index 00000000..2d0802a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services +{ + abstract class DisposableIpcService : IpcService, IDisposable + { + private int _disposeState; + + public DisposableIpcService(ServerBase server = null) : base(server) { } + + protected abstract void Dispose(bool isDisposing); + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/DummyService.cs b/src/Ryujinx.HLE/HOS/Services/DummyService.cs new file mode 100644 index 00000000..d69441a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/DummyService.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services +{ + class DummyService : IpcService + { + public string ServiceName { get; set; } + + public DummyService(string serviceName) + { + ServiceName = serviceName; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs new file mode 100644 index 00000000..52fe8702 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:r")] // 11.0.0+ + class IReaderForSystem : IpcService + { + public IReaderForSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs new file mode 100644 index 00000000..9401a6d7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:aw")] // 11.0.0+ + class IWriterForApplication : IpcService + { + public IWriterForApplication(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs new file mode 100644 index 00000000..621ec777 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:w")] // 11.0.0+ + class IWriterForSystem : IpcService + { + public IWriterForSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs new file mode 100644 index 00000000..9a689172 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:c")] + class IContext : IpcService + { + public IContext(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs new file mode 100644 index 00000000..6397afae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:r")] + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs b/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs new file mode 100644 index 00000000..34be7bdd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Es +{ + [Service("es")] + class IETicketService : IpcService + { + public IETicketService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs b/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs new file mode 100644 index 00000000..dd8705e6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:c")] + class IControl : IpcService + { + public IControl(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs new file mode 100644 index 00000000..85097878 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:r")] + class IRequest : IpcService + { + public IRequest(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs new file mode 100644 index 00000000..eb2c9553 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:p")] + class IPrivateService : IpcService + { + public IPrivateService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs new file mode 100644 index 00000000..aaa5c873 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Fatal.Types; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:u")] + class IService : IpcService + { + public IService(ServiceCtx context) { } + + [CommandCmif(0)] + // ThrowFatal(u64 result_code, u64 pid) + public ResultCode ThrowFatal(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + ulong pid = context.Request.HandleDesc.PId; + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, FatalPolicy.ErrorReportAndErrorScreen, null); + } + + [CommandCmif(1)] + // ThrowFatalWithPolicy(u64 result_code, u32 fatal_policy, u64 pid) + public ResultCode ThrowFatalWithPolicy(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32(); + ulong pid = context.Request.HandleDesc.PId; + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, null); + } + + [CommandCmif(2)] + // ThrowFatalWithCpuContext(u64 result_code, u32 fatal_policy, u64 pid, buffer<bytes, 0x15> cpu_context) + public ResultCode ThrowFatalWithCpuContext(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32(); + ulong pid = context.Request.HandleDesc.PId; + + ulong cpuContextPosition = context.Request.SendBuff[0].Position; + ulong cpuContextSize = context.Request.SendBuff[0].Size; + + ReadOnlySpan<byte> cpuContextData = context.Memory.GetSpan(cpuContextPosition, (int)cpuContextSize); + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, cpuContextData); + } + + private ResultCode ThrowFatalWithCpuContextImpl(ServiceCtx context, ResultCode resultCode, ulong pid, FatalPolicy fatalPolicy, ReadOnlySpan<byte> cpuContext) + { + StringBuilder errorReport = new StringBuilder(); + + errorReport.AppendLine(); + errorReport.AppendLine("ErrorReport log:"); + + errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}"); + errorReport.AppendLine($"\tPid: {pid}"); + errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); + errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); + + if (cpuContext != null) + { + errorReport.AppendLine("CPU Context:"); + + if (context.Device.Processes.ActiveApplication.Is64Bit) + { + CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0]; + + errorReport.AppendLine($"\tStartAddress: 0x{cpuContext64.StartAddress:x16}"); + errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext64.RegisterSetFlags}"); + + if (cpuContext64.StackTraceSize > 0) + { + errorReport.AppendLine("\tStackTrace:"); + + for (int i = 0; i < cpuContext64.StackTraceSize; i++) + { + errorReport.AppendLine($"\t\t0x{cpuContext64.StackTrace[i]:x16}"); + } + } + + errorReport.AppendLine("\tRegisters:"); + + for (int i = 0; i < cpuContext64.X.Length; i++) + { + errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext64.X[i]:x16}"); + } + + errorReport.AppendLine(); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext64.FP:x16}"); + errorReport.AppendLine($"\t\tLR:\t0x{cpuContext64.LR:x16}"); + errorReport.AppendLine($"\t\tSP:\t0x{cpuContext64.SP:x16}"); + errorReport.AppendLine($"\t\tPC:\t0x{cpuContext64.PC:x16}"); + errorReport.AppendLine($"\t\tPState:\t0x{cpuContext64.PState:x16}"); + errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext64.Afsr0:x16}"); + errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext64.Afsr1:x16}"); + errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext64.Esr:x16}"); + errorReport.AppendLine($"\t\tFar:\t0x{cpuContext64.Far:x16}"); + } + else + { + CpuContext32 cpuContext32 = MemoryMarshal.Cast<byte, CpuContext32>(cpuContext)[0]; + + errorReport.AppendLine($"\tStartAddress: 0x{cpuContext32.StartAddress:16}"); + errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext32.RegisterSetFlags}"); + + if (cpuContext32.StackTraceSize > 0) + { + errorReport.AppendLine("\tStackTrace:"); + + for (int i = 0; i < cpuContext32.StackTraceSize; i++) + { + errorReport.AppendLine($"\t\t0x{cpuContext32.StackTrace[i]:x16}"); + } + } + + errorReport.AppendLine("\tRegisters:"); + + for (int i = 0; i < cpuContext32.X.Length; i++) + { + errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext32.X[i]:x16}"); + } + + errorReport.AppendLine(); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.FP:x16}"); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.IP:x16}"); + errorReport.AppendLine($"\t\tSP:\t0x{cpuContext32.SP:x16}"); + errorReport.AppendLine($"\t\tLR:\t0x{cpuContext32.LR:x16}"); + errorReport.AppendLine($"\t\tPC:\t0x{cpuContext32.PC:x16}"); + errorReport.AppendLine($"\t\tPState:\t0x{cpuContext32.PState:x16}"); + errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext32.Afsr0:x16}"); + errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext32.Afsr1:x16}"); + errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext32.Esr:x16}"); + errorReport.AppendLine($"\t\tFar:\t0x{cpuContext32.Far:x16}"); + } + } + + Logger.Info?.Print(LogClass.ServiceFatal, errorReport.ToString()); + + context.Device.System.KernelContext.Syscall.Break((ulong)resultCode); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs new file mode 100644 index 00000000..5c0b116b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + public struct CpuContext32 + { + public Array11<uint> X; + public uint FP; + public uint IP; + public uint SP; + public uint LR; + public uint PC; + + public uint PState; + public uint Afsr0; + public uint Afsr1; + public uint Esr; + public uint Far; + + public Array32<uint> StackTrace; + public uint StackTraceSize; + public uint StartAddress; + public uint RegisterSetFlags; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs new file mode 100644 index 00000000..24829a78 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + public struct CpuContext64 + { + public Array29<ulong> X; + public ulong FP; + public ulong LR; + public ulong SP; + public ulong PC; + + public ulong PState; + public ulong Afsr0; + public ulong Afsr1; + public ulong Esr; + public ulong Far; + + public Array32<ulong> StackTrace; + public ulong StartAddress; + public ulong RegisterSetFlags; + public uint StackTraceSize; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs new file mode 100644 index 00000000..fe55cf12 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + enum FatalPolicy + { + ErrorReportAndErrorScreen, + ErrorReport, + ErrorScreen + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs new file mode 100644 index 00000000..d5258a82 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs @@ -0,0 +1,55 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator; + +namespace Ryujinx.HLE.HOS.Services.Friend +{ + [Service("friend:a", FriendServicePermissionLevel.Administrator)] + [Service("friend:m", FriendServicePermissionLevel.Manager)] + [Service("friend:s", FriendServicePermissionLevel.System)] + [Service("friend:u", FriendServicePermissionLevel.User)] + [Service("friend:v", FriendServicePermissionLevel.Viewer)] + class IServiceCreator : IpcService + { + private FriendServicePermissionLevel _permissionLevel; + + public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [CommandCmif(0)] + // CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService> + public ResultCode CreateFriendService(ServiceCtx context) + { + MakeObject(context, new IFriendService(_permissionLevel)); + + return ResultCode.Success; + } + + [CommandCmif(1)] // 2.0.0+ + // CreateNotificationService(nn::account::Uid userId) -> object<nn::friends::detail::ipc::INotificationService> + public ResultCode CreateNotificationService(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + MakeObject(context, new INotificationService(context, userId, _permissionLevel)); + + return ResultCode.Success; + } + + [CommandCmif(2)] // 4.0.0+ + // CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService> + public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context) + { + MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs new file mode 100644 index 00000000..3e66e873 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Friend +{ + enum ResultCode + { + ModuleId = 121, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (2 << ErrorCodeShift) | ModuleId, + InternetRequestDenied = (6 << ErrorCodeShift) | ModuleId, + NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs new file mode 100644 index 00000000..87f54bf3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)] + struct Friend + { + public UserId UserId; + public long NetworkUserId; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)] + public string Nickname; + + public UserPresence presence; + + [MarshalAs(UnmanagedType.I1)] + public bool IsFavourite; + + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)] + char[] Unknown; + + [MarshalAs(UnmanagedType.I1)] + public bool IsValid; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs new file mode 100644 index 00000000..261bf7bf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential)] + struct FriendFilter + { + public PresenceStatusFilter PresenceStatus; + + [MarshalAs(UnmanagedType.I1)] + public bool IsFavoriteOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsSameAppPresenceOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsSameAppPlayedOnly; + + [MarshalAs(UnmanagedType.I1)] + public bool IsArbitraryAppPlayedOnly; + + public long PresenceGroupId; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs new file mode 100644 index 00000000..df2e6525 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + enum PresenceStatus : uint + { + Offline, + Online, + OnlinePlay + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs new file mode 100644 index 00000000..24da7fd3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + enum PresenceStatusFilter : uint + { + None, + Online, + OnlinePlay, + OnlineOrOnlinePlay + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs new file mode 100644 index 00000000..d36b3f31 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x8)] + struct UserPresence + { + public UserId UserId; + public long LastTimeOnlineTimestamp; + public PresenceStatus Status; + + [MarshalAs(UnmanagedType.I1)] + public bool SamePresenceGroupApplication; + + public Array3<byte> Unknown; + private AppKeyValueStorageHolder _appKeyValueStorage; + + public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size)); + + [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)] + private struct AppKeyValueStorageHolder + { + public const int Size = 0xC0; + } + + public override string ToString() + { + return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}"; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs new file mode 100644 index 00000000..42b34312 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class IDaemonSuspendSessionService : IpcService + { + private FriendServicePermissionLevel PermissionLevel; + + public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel) + { + PermissionLevel = permissionLevel; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs new file mode 100644 index 00000000..2858aa46 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -0,0 +1,352 @@ +using LibHac.Ns; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService; +using Ryujinx.Horizon.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class IFriendService : IpcService + { + private FriendServicePermissionLevel _permissionLevel; + private KEvent _completionEvent; + + public IFriendService(FriendServicePermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [CommandCmif(0)] + // GetCompletionEvent() -> handle<copy> + public ResultCode GetCompletionEvent(ServiceCtx context) + { + if (_completionEvent == null) + { + _completionEvent = new KEvent(context.Device.System.KernelContext); + } + + if (context.Process.HandleTable.GenerateHandle(_completionEvent.ReadableEvent, out int completionEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // nn::friends::Cancel() + public ResultCode Cancel(ServiceCtx context) + { + // TODO: Original service sets an internal field to 1 here. Determine usage. + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return ResultCode.Success; + } + + [CommandCmif(10100)] + // nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) + // -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa> + public ResultCode GetFriendListIds(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UserId userId = context.RequestData.ReadStruct<UserId>(); + FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new + { + UserId = userId.ToString(), + offset, + filter.PresenceStatus, + filter.IsFavoriteOnly, + filter.IsSameAppPresenceOnly, + filter.IsSameAppPlayedOnly, + filter.IsArbitraryAppPlayedOnly, + filter.PresenceGroupId, + }); + + return ResultCode.Success; + } + + [CommandCmif(10101)] + // nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) + // -> int outCount, array<nn::friends::detail::FriendImpl, 0x6> + public ResultCode GetFriendList(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UserId userId = context.RequestData.ReadStruct<UserId>(); + FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { + UserId = userId.ToString(), + offset, + filter.PresenceStatus, + filter.IsFavoriteOnly, + filter.IsSameAppPresenceOnly, + filter.IsSameAppPlayedOnly, + filter.IsArbitraryAppPlayedOnly, + filter.PresenceGroupId, + }); + + return ResultCode.Success; + } + + [CommandCmif(10120)] // 10.0.0+ + // nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool + public ResultCode IsFriendListCacheAvailable(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise. + // NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check. + context.ResponseData.Write(true); + + // TODO: Since we don't support friend features, it's fine to stub it for now. + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandCmif(10121)] // 10.0.0+ + // nn::friends::EnsureFriendListAvailable(nn::account::Uid userId) + public ResultCode EnsureFriendListAvailable(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id. + // Since we don't support friend features, it's fine to stub it for now. + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandCmif(10400)] + // nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>) + public ResultCode GetBlockedUserListIds(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UserId userId = context.RequestData.ReadStruct<UserId>(); + + // There are no friends blocked, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { offset, UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandCmif(10600)] + // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId) + public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + context.Device.System.AccountManager.OpenUserOnlinePlay(userId); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandCmif(10601)] + // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId) + public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + context.Device.System.AccountManager.CloseUserOnlinePlay(userId); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() }); + + return ResultCode.Success; + } + + [CommandCmif(10610)] + // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>) + public ResultCode UpdateUserPresence(ServiceCtx context) + { + UserId uuid = context.RequestData.ReadStruct<UserId>(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + ulong position = context.Request.PtrBuff[0].Position; + ulong size = context.Request.PtrBuff[0].Size; + + ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size)); + + if (uuid.IsNull) + { + return ResultCode.InvalidArgument; + } + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() }); + + return ResultCode.Success; + } + + [CommandCmif(10700)] + // nn::friends::GetPlayHistoryRegistrationKey(b8 unknown, nn::account::Uid) -> buffer<nn::friends::PlayHistoryRegistrationKey, 0x1a> + public ResultCode GetPlayHistoryRegistrationKey(ServiceCtx context) + { + bool unknownBool = context.RequestData.ReadBoolean(); + UserId userId = context.RequestData.ReadStruct<UserId>(); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x40UL); + + ulong bufferPosition = context.Request.RecvListBuff[0].Position; + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance. + + byte[] randomBytes = new byte[8]; + + Random.Shared.NextBytes(randomBytes); + + // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance. + // Then call nn::friends::detail::service::core::AccountStorageManager::GetInstance and store the instance. + // Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid. + // And store it in the savedata 8000000000000080 in the friends:/uid.bin file. + + Array16<byte> randomGuid = new Array16<byte>(); + + Guid.NewGuid().ToByteArray().AsSpan().CopyTo(randomGuid.AsSpan()); + + PlayHistoryRegistrationKey playHistoryRegistrationKey = new PlayHistoryRegistrationKey + { + Type = 0x101, + KeyIndex = (byte)(randomBytes[0] & 7), + UserIdBool = 0, // TODO: Find it. + UnknownBool = (byte)(unknownBool ? 1 : 0), // TODO: Find it. + Reserved = new Array11<byte>(), + Uuid = randomGuid + }; + + ReadOnlySpan<byte> playHistoryRegistrationKeyBuffer = SpanHelpers.AsByteSpan(ref playHistoryRegistrationKey); + + /* + + NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer). + We currently don't support play history and online services so we can use a blank key for now. + Code for reference: + + byte[] hmacKey = new byte[0x20]; + + HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey); + byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer); + + */ + + context.Memory.Write(bufferPosition, playHistoryRegistrationKeyBuffer); + context.Memory.Write(bufferPosition + 0x20, new byte[0x20]); // HmacHash + + return ResultCode.Success; + } + + [CommandCmif(10702)] + // nn::friends::AddPlayHistory(nn::account::Uid, u64, pid, buffer<nn::friends::PlayHistoryRegistrationKey, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>) + public ResultCode AddPlayHistory(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + // Pid placeholder + context.RequestData.ReadInt64(); + ulong pid = context.Request.HandleDesc.PId; + + ulong playHistoryRegistrationKeyPosition = context.Request.PtrBuff[0].Position; + ulong PlayHistoryRegistrationKeySize = context.Request.PtrBuff[0].Size; + + ulong inAppScreenName1Position = context.Request.PtrBuff[1].Position; + ulong inAppScreenName1Size = context.Request.PtrBuff[1].Size; + + ulong inAppScreenName2Position = context.Request.PtrBuff[2].Position; + ulong inAppScreenName2Size = context.Request.PtrBuff[2].Size; + + if (userId.IsNull || inAppScreenName1Size > 0x48 || inAppScreenName2Size > 0x48) + { + return ResultCode.InvalidArgument; + } + + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + /* + + NOTE: The service calls nn::friends::detail::service::core::PlayHistoryManager to store informations using the registration key computed in GetPlayHistoryRegistrationKey. + Then calls nn::friends::detail::service::core::FriendListManager to update informations on the friend list. + We currently don't support play history and online services so it's fine to do nothing. + + */ + + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs new file mode 100644 index 00000000..063750c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + class INotificationService : DisposableIpcService + { + private readonly UserId _userId; + private readonly FriendServicePermissionLevel _permissionLevel; + + private readonly object _lock = new object(); + + private KEvent _notificationEvent; + private int _notificationEventHandle = 0; + + private LinkedList<NotificationInfo> _notifications; + + private bool _hasNewFriendRequest; + private bool _hasFriendListUpdate; + + public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel) + { + _userId = userId; + _permissionLevel = permissionLevel; + _notifications = new LinkedList<NotificationInfo>(); + _notificationEvent = new KEvent(context.Device.System.KernelContext); + + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + NotificationEventHandler.Instance.RegisterNotificationService(this); + } + + [CommandCmif(0)] //2.0.0+ + // nn::friends::detail::ipc::INotificationService::GetEvent() -> handle<copy> + public ResultCode GetEvent(ServiceCtx context) + { + if (_notificationEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1)] //2.0.0+ + // nn::friends::detail::ipc::INotificationService::Clear() + public ResultCode Clear(ServiceCtx context) + { + lock (_lock) + { + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + _notifications.Clear(); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] // 2.0.0+ + // nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo + public ResultCode Pop(ServiceCtx context) + { + lock (_lock) + { + if (_notifications.Count >= 1) + { + NotificationInfo notificationInfo = _notifications.First.Value; + _notifications.RemoveFirst(); + + if (notificationInfo.Type == NotificationEventType.FriendListUpdate) + { + _hasFriendListUpdate = false; + } + else if (notificationInfo.Type == NotificationEventType.NewFriendRequest) + { + _hasNewFriendRequest = false; + } + + context.ResponseData.WriteStruct(notificationInfo); + + return ResultCode.Success; + } + } + + return ResultCode.NotificationQueueEmpty; + } + + public void SignalFriendListUpdate(UserId targetId) + { + lock (_lock) + { + if (_userId == targetId) + { + if (!_hasFriendListUpdate) + { + NotificationInfo friendListNotification = new NotificationInfo(); + + if (_notifications.Count != 0) + { + friendListNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + friendListNotification.Type = NotificationEventType.FriendListUpdate; + _hasFriendListUpdate = true; + + if (_hasNewFriendRequest) + { + NotificationInfo newFriendRequestNotification = new NotificationInfo(); + + if (_notifications.Count != 0) + { + newFriendRequestNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; + _notifications.AddFirst(newFriendRequestNotification); + } + + // We defer this to make sure we are on top of the queue. + _notifications.AddFirst(friendListNotification); + } + + _notificationEvent.ReadableEvent.Signal(); + } + } + } + + public void SignalNewFriendRequest(UserId targetId) + { + lock (_lock) + { + if ((_permissionLevel & FriendServicePermissionLevel.ViewerMask) != 0 && _userId == targetId) + { + if (!_hasNewFriendRequest) + { + if (_notifications.Count == 100) + { + SignalFriendListUpdate(targetId); + } + + NotificationInfo newFriendRequestNotification = new NotificationInfo + { + Type = NotificationEventType.NewFriendRequest + }; + + _notifications.AddLast(newFriendRequestNotification); + _hasNewFriendRequest = true; + } + + _notificationEvent.ReadableEvent.Signal(); + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + NotificationEventHandler.Instance.UnregisterNotificationService(this); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs new file mode 100644 index 00000000..383ad006 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs @@ -0,0 +1,83 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + public sealed class NotificationEventHandler + { + private static NotificationEventHandler instance; + private static object instanceLock = new object(); + + private INotificationService[] _registry; + + public static NotificationEventHandler Instance + { + get + { + lock (instanceLock) + { + if (instance == null) + { + instance = new NotificationEventHandler(); + } + + return instance; + } + } + } + + NotificationEventHandler() + { + _registry = new INotificationService[0x20]; + } + + internal void RegisterNotificationService(INotificationService service) + { + // NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == null) + { + _registry[i] = service; + break; + } + } + } + + internal void UnregisterNotificationService(INotificationService service) + { + // NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == service) + { + _registry[i] = null; + break; + } + } + } + + // TODO: Use this when we will have enough things to go online. + public void SignalFriendListUpdate(UserId targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] != null) + { + _registry[i].SignalFriendListUpdate(targetId); + } + } + } + + // TODO: Use this when we will have enough things to go online. + public void SignalNewFriendRequest(UserId targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] != null) + { + _registry[i].SignalNewFriendRequest(targetId); + } + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs new file mode 100644 index 00000000..5136ae8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + enum NotificationEventType : uint + { + Invalid = 0x0, + FriendListUpdate = 0x1, + NewFriendRequest = 0x65 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs new file mode 100644 index 00000000..e710bf06 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct NotificationInfo + { + public NotificationEventType Type; + private Array4<byte> _padding; + public long NetworkUserIdPlaceholder; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs new file mode 100644 index 00000000..12a3d42f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + [Flags] + enum FriendServicePermissionLevel + { + UserMask = 1, + ViewerMask = 2, + ManagerMask = 4, + SystemMask = 8, + + Administrator = -1, + User = UserMask, + Viewer = UserMask | ViewerMask, + Manager = UserMask | ViewerMask | ManagerMask, + System = UserMask | SystemMask + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs new file mode 100644 index 00000000..32d962c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct PlayHistoryRegistrationKey + { + public ushort Type; + public byte KeyIndex; + public byte UserIdBool; + public byte UnknownBool; + public Array11<byte> Reserved; + public Array16<byte> Uuid; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs new file mode 100644 index 00000000..ba924db8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -0,0 +1,161 @@ +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using LibHac.Spl; +using LibHac.Tools.Es; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + static class FileSystemProxyHelper + { + public static ResultCode OpenNsp(ServiceCtx context, string pfsPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); + using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(new PartitionFileSystem(storage)); + + ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet); + + using SharedRef<LibHac.FsSrv.Sf.IFileSystem> adapter = FileSystemInterfaceAdapter.CreateShared(ref nsp.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenNcaFs(ServiceCtx context, string ncaPath, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); + + if (!nca.SectionExists(NcaSectionType.Data)) + { + return ResultCode.PartitionNotFound; + } + + LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using var sharedFs = new SharedRef<LibHac.Fs.Fsa.IFileSystem>(fileSystem); + + using SharedRef<LibHac.FsSrv.Sf.IFileSystem> adapter = FileSystemInterfaceAdapter.CreateShared(ref sharedFs.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; + + while (string.IsNullOrWhiteSpace(archivePath.Extension)) + { + archivePath = archivePath.Parent; + } + + if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) + { + FileStream pfsFile = new FileStream( + archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), + FileMode.Open, + FileAccess.Read); + + try + { + PartitionFileSystem nsp = new PartitionFileSystem(pfsFile.AsStorage()); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); + + using var ncaFile = new UniqueRef<LibHac.Fs.Fsa.IFile>(); + + Result result = nsp.OpenFile(ref ncaFile.Ref, filename.ToU8Span(), OpenMode.Read); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + return OpenNcaFs(context, fullPath, ncaFile.Release().AsStorage(), out openedFileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + return ResultCode.PathDoesNotExist; + } + + public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, KeySet keySet) + { + foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) + { + using var ticketFile = new UniqueRef<LibHac.Fs.Fsa.IFile>(); + + Result result = nsp.OpenFile(ref ticketFile.Ref, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new Ticket(ticketFile.Get.AsStream()); + var titleKey = ticket.GetTitleKey(keySet); + + if (titleKey != null) + { + keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(titleKey)); + } + } + } + } + + public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer); + + return ref fspBuffer[0]; + } + + public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer); + + return ref pathBuffer[0]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs new file mode 100644 index 00000000..b9759449 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs @@ -0,0 +1,52 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IDirectory : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IDirectory> _baseDirectory; + + public IDirectory(ref SharedRef<LibHac.FsSrv.Sf.IDirectory> directory) + { + _baseDirectory = SharedRef<LibHac.FsSrv.Sf.IDirectory>.CreateMove(ref directory); + } + + [CommandCmif(0)] + // Read() -> (u64 count, buffer<nn::fssrv::sf::IDirectoryEntry, 6, 0> entries) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _baseDirectory.Get.Read(out long entriesRead, new OutBuffer(region.Memory.Span)); + + context.ResponseData.Write(entriesRead); + + return (ResultCode)result.Value; + } + } + + [CommandCmif(1)] + // GetEntryCount() -> u64 + public ResultCode GetEntryCount(ServiceCtx context) + { + Result result = _baseDirectory.Get.GetEntryCount(out long entryCount); + + context.ResponseData.Write(entryCount); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseDirectory.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs new file mode 100644 index 00000000..4bc58ae5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs @@ -0,0 +1,95 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Sf; +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFile : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IFile> _baseFile; + + public IFile(ref SharedRef<LibHac.FsSrv.Sf.IFile> baseFile) + { + _baseFile = SharedRef<LibHac.FsSrv.Sf.IFile>.CreateMove(ref baseFile); + } + + [CommandCmif(0)] + // Read(u32 readOption, u64 offset, u64 size) -> (u64 out_size, buffer<u8, 0x46, 0> out_buf) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + ReadOption readOption = context.RequestData.ReadStruct<ReadOption>(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _baseFile.Get.Read(out long bytesRead, offset, new OutBuffer(region.Memory.Span), size, readOption); + + context.ResponseData.Write(bytesRead); + + return (ResultCode)result.Value; + } + } + + [CommandCmif(1)] + // Write(u32 writeOption, u64 offset, u64 size, buffer<u8, 0x45, 0>) + public ResultCode Write(ServiceCtx context) + { + ulong position = context.Request.SendBuff[0].Position; + + WriteOption writeOption = context.RequestData.ReadStruct<WriteOption>(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + byte[] data = new byte[context.Request.SendBuff[0].Size]; + + context.Memory.Read(position, data); + + return (ResultCode)_baseFile.Get.Write(offset, new InBuffer(data), size, writeOption).Value; + } + + [CommandCmif(2)] + // Flush() + public ResultCode Flush(ServiceCtx context) + { + return (ResultCode)_baseFile.Get.Flush().Value; + } + + [CommandCmif(3)] + // SetSize(u64 size) + public ResultCode SetSize(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFile.Get.SetSize(size).Value; + } + + [CommandCmif(4)] + // GetSize() -> u64 fileSize + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseFile.Get.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseFile.Destroy(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs new file mode 100644 index 00000000..9effa79d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs @@ -0,0 +1,213 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using Path = LibHac.FsSrv.Sf.Path; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFileSystem : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IFileSystem> _fileSystem; + + public IFileSystem(ref SharedRef<LibHac.FsSrv.Sf.IFileSystem> provider) + { + _fileSystem = SharedRef<LibHac.FsSrv.Sf.IFileSystem>.CreateMove(ref provider); + } + + public SharedRef<LibHac.FsSrv.Sf.IFileSystem> GetBaseFileSystem() + { + return SharedRef<LibHac.FsSrv.Sf.IFileSystem>.CreateCopy(in _fileSystem); + } + + [CommandCmif(0)] + // CreateFile(u32 createOption, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode CreateFile(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + int createOption = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; + + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_fileSystem.Get.CreateFile(in name, size, createOption).Value; + } + + [CommandCmif(1)] + // DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode DeleteFile(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteFile(in name).Value; + } + + [CommandCmif(2)] + // CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode CreateDirectory(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.CreateDirectory(in name).Value; + } + + [CommandCmif(3)] + // DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode DeleteDirectory(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteDirectory(in name).Value; + } + + [CommandCmif(4)] + // DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode DeleteDirectoryRecursively(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteDirectoryRecursively(in name).Value; + } + + [CommandCmif(5)] + // RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath) + public ResultCode RenameFile(ServiceCtx context) + { + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); + + return (ResultCode)_fileSystem.Get.RenameFile(in currentName, in newName).Value; + } + + [CommandCmif(6)] + // RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath) + public ResultCode RenameDirectory(ServiceCtx context) + { + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); + + return (ResultCode)_fileSystem.Get.RenameDirectory(in currentName, in newName).Value; + } + + [CommandCmif(7)] + // GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType + public ResultCode GetEntryType(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetEntryType(out uint entryType, in name); + + context.ResponseData.Write((int)entryType); + + return (ResultCode)result.Value; + } + + [CommandCmif(8)] + // OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file + public ResultCode OpenFile(ServiceCtx context) + { + uint mode = context.RequestData.ReadUInt32(); + + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + using var file = new SharedRef<LibHac.FsSrv.Sf.IFile>(); + + Result result = _fileSystem.Get.OpenFile(ref file.Ref, in name, mode); + + if (result.IsSuccess()) + { + IFile fileInterface = new IFile(ref file.Ref); + + MakeObject(context, fileInterface); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(9)] + // OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory + public ResultCode OpenDirectory(ServiceCtx context) + { + uint mode = context.RequestData.ReadUInt32(); + + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + using var dir = new SharedRef<LibHac.FsSrv.Sf.IDirectory>(); + + Result result = _fileSystem.Get.OpenDirectory(ref dir.Ref, name, mode); + + if (result.IsSuccess()) + { + IDirectory dirInterface = new IDirectory(ref dir.Ref); + + MakeObject(context, dirInterface); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(10)] + // Commit() + public ResultCode Commit(ServiceCtx context) + { + return (ResultCode)_fileSystem.Get.Commit().Value; + } + + [CommandCmif(11)] + // GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace + public ResultCode GetFreeSpaceSize(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetFreeSpaceSize(out long size, in name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [CommandCmif(12)] + // GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize + public ResultCode GetTotalSpaceSize(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetTotalSpaceSize(out long size, in name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [CommandCmif(13)] + // CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path) + public ResultCode CleanDirectoryRecursively(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.CleanDirectoryRecursively(in name).Value; + } + + [CommandCmif(14)] + // GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp + public ResultCode GetFileTimeStampRaw(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, in name); + + context.ResponseData.Write(timestamp.Created); + context.ResponseData.Write(timestamp.Modified); + context.ResponseData.Write(timestamp.Accessed); + context.ResponseData.Write(1L); // Is valid? + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _fileSystem.Destroy(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs new file mode 100644 index 00000000..54c7b800 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -0,0 +1,65 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; +using Ryujinx.HLE.HOS.Ipc; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IStorage : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IStorage> _baseStorage; + + public IStorage(ref SharedRef<LibHac.FsSrv.Sf.IStorage> baseStorage) + { + _baseStorage = SharedRef<LibHac.FsSrv.Sf.IStorage>.CreateMove(ref baseStorage); + } + + [CommandCmif(0)] + // Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> buffer + public ResultCode Read(ServiceCtx context) + { + ulong offset = context.RequestData.ReadUInt64(); + ulong size = context.RequestData.ReadUInt64(); + + if (context.Request.ReceiveBuff.Count > 0) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + // Use smaller length to avoid overflows. + if (size > bufferLen) + { + size = bufferLen; + } + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size); + + return (ResultCode)result.Value; + } + } + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSize() -> u64 size + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseStorage.Get.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseStorage.Destroy(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs new file mode 100644 index 00000000..2a40aea4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs @@ -0,0 +1,58 @@ +using LibHac; +using LibHac.Common; + +using GameCardHandle = System.UInt32; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class IDeviceOperator : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IDeviceOperator> _baseOperator; + + public IDeviceOperator(ref SharedRef<LibHac.FsSrv.Sf.IDeviceOperator> baseOperator) + { + _baseOperator = SharedRef<LibHac.FsSrv.Sf.IDeviceOperator>.CreateMove(ref baseOperator); + } + + [CommandCmif(0)] + // IsSdCardInserted() -> b8 is_inserted + public ResultCode IsSdCardInserted(ServiceCtx context) + { + Result result = _baseOperator.Get.IsSdCardInserted(out bool isInserted); + + context.ResponseData.Write(isInserted); + + return (ResultCode)result.Value; + } + + [CommandCmif(200)] + // IsGameCardInserted() -> b8 is_inserted + public ResultCode IsGameCardInserted(ServiceCtx context) + { + Result result = _baseOperator.Get.IsGameCardInserted(out bool isInserted); + + context.ResponseData.Write(isInserted); + + return (ResultCode)result.Value; + } + + [CommandCmif(202)] + // GetGameCardHandle() -> u32 gamecard_handle + public ResultCode GetGameCardHandle(ServiceCtx context) + { + Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle); + + context.ResponseData.Write(handle); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseOperator.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs new file mode 100644 index 00000000..e961e9b1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -0,0 +1,1308 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Impl; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Spl; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; +using System; +using System.IO; +using static Ryujinx.HLE.Utilities.StringUtils; +using GameCardHandle = System.UInt32; +using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; +using IStorage = LibHac.FsSrv.Sf.IStorage; +using RightsId = LibHac.Fs.RightsId; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-srv")] + class IFileSystemProxy : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy; + private ulong _pid; + + public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer) + { + var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient; + _baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject(); + } + + [CommandCmif(1)] + // SetCurrentProcess(u64, pid) + public ResultCode SetCurrentProcess(ServiceCtx context) + { + _pid = context.Request.HandleDesc.PId; + + return ResultCode.Success; + } + + [CommandCmif(8)] + // OpenFileSystemWithId(nn::fssrv::sf::FileSystemType filesystem_type, nn::ApplicationId tid, buffer<bytes<0x301>, 0x19, 0x301> path) + // -> object<nn::fssrv::sf::IFileSystem> contentFs + public ResultCode OpenFileSystemWithId(ServiceCtx context) + { + FileSystemType fileSystemType = (FileSystemType)context.RequestData.ReadInt32(); + ulong titleId = context.RequestData.ReadUInt64(); + string switchPath = ReadUtf8String(context); + string fullPath = context.Device.FileSystem.SwitchPathToSystemPath(switchPath); + + if (!File.Exists(fullPath)) + { + if (fullPath.Contains(".")) + { + ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.PathDoesNotExist; + } + + FileStream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read); + string extension = System.IO.Path.GetExtension(fullPath); + + if (extension == ".nca") + { + ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else if (extension == ".nsp") + { + ResultCode result = FileSystemProxyHelper.OpenNsp(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.InvalidInput; + } + + [CommandCmif(11)] + // OpenBisFileSystem(nn::fssrv::sf::Partition partitionID, buffer<bytes<0x301>, 0x19, 0x301>) -> object<nn::fssrv::sf::IFileSystem> Bis + public ResultCode OpenBisFileSystem(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenBisFileSystem(ref fileSystem.Ref, in path, bisPartitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // OpenBisStorage(u32 partitionId) -> object<nn::fssrv::sf::IStorage> bisStorage + public ResultCode OpenBisStorage(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + using var storage = new SharedRef<IStorage>(); + + Result result = _baseFileSystemProxy.Get.OpenBisStorage(ref storage.Ref, bisPartitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IStorage(ref storage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // InvalidateBisCache() -> () + public ResultCode InvalidateBisCache(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.InvalidateBisCache().Value; + } + + [CommandCmif(18)] + // OpenSdCardFileSystem() -> object<nn::fssrv::sf::IFileSystem> + public ResultCode OpenSdCardFileSystem(ServiceCtx context) + { + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenSdCardFileSystem(ref fileSystem.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(19)] + // FormatSdCardFileSystem() -> () + public ResultCode FormatSdCardFileSystem(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.FormatSdCardFileSystem().Value; + } + + [CommandCmif(21)] + // DeleteSaveDataFileSystem(u64 saveDataId) -> () + public ResultCode DeleteSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystem(saveDataId).Value; + } + + [CommandCmif(22)] + // CreateSaveDataFileSystem(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo) -> () + public ResultCode CreateSaveDataFileSystem(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct<SaveDataCreationInfo>(); + SaveDataMetaInfo metaInfo = context.RequestData.ReadStruct<SaveDataMetaInfo>(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo).Value; + } + + [CommandCmif(23)] + // CreateSaveDataFileSystemBySystemSaveDataId(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo) -> () + public ResultCode CreateSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct<SaveDataCreationInfo>(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo).Value; + } + + [CommandCmif(24)] + // RegisterSaveDataFileSystemAtomicDeletion(buffer<u64, 5> saveDataIds) -> () + public ResultCode RegisterSaveDataFileSystemAtomicDeletion(ServiceCtx context) + { + byte[] saveIdBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, saveIdBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterSaveDataFileSystemAtomicDeletion(new InBuffer(saveIdBuffer)).Value; + } + + [CommandCmif(25)] + // DeleteSaveDataFileSystemBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> () + public ResultCode DeleteSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } + + [CommandCmif(26)] + // FormatSdCardDryRun() -> () + public ResultCode FormatSdCardDryRun(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.FormatSdCardDryRun().Value; + } + + [CommandCmif(27)] + // IsExFatSupported() -> (u8 isSupported) + public ResultCode IsExFatSupported(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.IsExFatSupported(out bool isSupported); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(isSupported); + + return ResultCode.Success; + } + + [CommandCmif(28)] + // DeleteSaveDataFileSystemBySaveDataAttribute(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> () + public ResultCode DeleteSaveDataFileSystemBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute).Value; + } + + [CommandCmif(30)] + // OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage> + public ResultCode OpenGameCardStorage(ServiceCtx context) + { + GameCardHandle handle = context.RequestData.ReadUInt32(); + GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); + using var storage = new SharedRef<IStorage>(); + + Result result = _baseFileSystemProxy.Get.OpenGameCardStorage(ref storage.Ref, handle, partitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IStorage(ref storage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem> + public ResultCode OpenGameCardFileSystem(ServiceCtx context) + { + GameCardHandle handle = context.RequestData.ReadUInt32(); + GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenGameCardFileSystem(ref fileSystem.Ref, handle, partitionId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(32)] + // ExtendSaveDataFileSystem(u8 spaceId, u64 saveDataId, s64 dataSize, s64 journalSize) -> () + public ResultCode ExtendSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize).Value; + } + + [CommandCmif(33)] + // DeleteCacheStorage(u16 index) -> () + public ResultCode DeleteCacheStorage(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteCacheStorage(index).Value; + } + + [CommandCmif(34)] + // GetCacheStorageSize(u16 index) -> (s64 dataSize, s64 journalSize) + public ResultCode GetCacheStorageSize(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + Result result = _baseFileSystemProxy.Get.GetCacheStorageSize(out long dataSize, out long journalSize, index); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(dataSize); + context.ResponseData.Write(journalSize); + + return ResultCode.Success; + } + + [CommandCmif(35)] + // CreateSaveDataFileSystemWithHashSalt(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo nn::fs::HashSalt hashSalt) -> () + public ResultCode CreateSaveDataFileSystemWithHashSalt(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct<SaveDataCreationInfo>(); + SaveDataMetaInfo metaCreateInfo = context.RequestData.ReadStruct<SaveDataMetaInfo>(); + HashSalt hashSalt = context.RequestData.ReadStruct<HashSalt>(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value; + } + + [CommandCmif(37)] // 14.0.0+ + // CreateSaveDataFileSystemWithCreationInfo2(buffer<nn::fs::SaveDataCreationInfo2, 25> creationInfo) -> () + public ResultCode CreateSaveDataFileSystemWithCreationInfo2(ServiceCtx context) + { + byte[] creationInfoBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, creationInfoBuffer); + ref readonly SaveDataCreationInfo2 creationInfo = ref SpanHelpers.AsReadOnlyStruct<SaveDataCreationInfo2>(creationInfoBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Value; + } + + [CommandCmif(51)] + // OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs + public ResultCode OpenSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(52)] + // OpenSaveDataFileSystemBySystemSaveDataId(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> systemSaveDataFs + public ResultCode OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataFileSystemBySystemSaveDataId(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(53)] + // OpenReadOnlySaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> + public ResultCode OpenReadOnlySaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenReadOnlySaveDataFileSystem(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(57)] + // ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> (buffer<nn::fs::SaveDataExtraData, 6> extraData) + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(new OutBuffer(extraDataBuffer), spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandCmif(58)] + // ReadSaveDataFileSystemExtraData(u64 saveDataId) -> (buffer<nn::fs::SaveDataExtraData, 6> extraData) + public ResultCode ReadSaveDataFileSystemExtraData(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraData(new OutBuffer(extraDataBuffer), saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandCmif(59)] + // WriteSaveDataFileSystemExtraData(u8 spaceId, u64 saveDataId, buffer<nn::fs::SaveDataExtraData, 5> extraData) -> () + public ResultCode WriteSaveDataFileSystemExtraData(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, new InBuffer(extraDataBuffer)).Value; + } + + [CommandCmif(60)] + // OpenSaveDataInfoReader() -> object<nn::fssrv::sf::ISaveDataInfoReader> + public ResultCode OpenSaveDataInfoReader(ServiceCtx context) + { + using var infoReader = new SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReader(ref infoReader.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // OpenSaveDataInfoReaderBySaveDataSpaceId(u8 spaceId) -> object<nn::fssrv::sf::ISaveDataInfoReader> + public ResultCode OpenSaveDataInfoReaderBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadByte(); + using var infoReader = new SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderBySaveDataSpaceId(ref infoReader.Ref, spaceId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // OpenSaveDataInfoReaderOnlyCacheStorage() -> object<nn::fssrv::sf::ISaveDataInfoReader> + public ResultCode OpenSaveDataInfoReaderOnlyCacheStorage(ServiceCtx context) + { + using var infoReader = new SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderOnlyCacheStorage(ref infoReader.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(64)] + // OpenSaveDataInternalStorageFileSystem(u8 spaceId, u64 saveDataId) -> object<nn::fssrv::sf::ISaveDataInfoReader> + public ResultCode OpenSaveDataInternalStorageFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInternalStorageFileSystem(ref fileSystem.Ref, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // UpdateSaveDataMacForDebug(u8 spaceId, u64 saveDataId) -> () + public ResultCode UpdateSaveDataMacForDebug(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId).Value; + } + + [CommandCmif(66)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMask(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; + } + + [CommandCmif(67)] + public ResultCode FindSaveDataWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct<SaveDataFilter>(); + + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _baseFileSystemProxy.Get.FindSaveDataWithFilter(out long count, new OutBuffer(region.Memory.Span), spaceId, in filter); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(count); + } + + return ResultCode.Success; + } + + [CommandCmif(68)] + public ResultCode OpenSaveDataInfoReaderWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct<SaveDataFilter>(); + using var infoReader = new SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderWithFilter(ref infoReader.Ref, spaceId, in filter); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(69)] + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandCmif(70)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; + } + + [CommandCmif(71)] + public ResultCode ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + + byte[] maskBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, maskBuffer); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute, new InBuffer(maskBuffer)); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandCmif(80)] + public ResultCode OpenSaveDataMetaFile(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt32(); + SaveDataMetaType metaType = (SaveDataMetaType)context.RequestData.ReadInt32(); + SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>(); + using var file = new SharedRef<LibHac.FsSrv.Sf.IFile>(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataMetaFile(ref file.Ref, spaceId, in attribute, metaType); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new IFile(ref file.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(84)] + public ResultCode ListAccessibleSaveDataOwnerId(ServiceCtx context) + { + int startIndex = context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + ProgramId programId = context.RequestData.ReadStruct<ProgramId>(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ListAccessibleSaveDataOwnerId(out int readCount, new OutBuffer(outputBuffer), programId, startIndex, bufferCount); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(readCount); + + return ResultCode.Success; + } + + [CommandCmif(100)] + public ResultCode OpenImageDirectoryFileSystem(ServiceCtx context) + { + ImageDirectoryId directoryId = (ImageDirectoryId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenImageDirectoryFileSystem(ref fileSystem.Ref, directoryId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(101)] + public ResultCode OpenBaseFileSystem(ServiceCtx context) + { + BaseFileSystemId fileSystemId = (BaseFileSystemId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenBaseFileSystem(ref fileSystem.Ref, fileSystemId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(110)] + public ResultCode OpenContentStorageFileSystem(ServiceCtx context) + { + ContentStorageId contentStorageId = (ContentStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenContentStorageFileSystem(ref fileSystem.Ref, contentStorageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(120)] + public ResultCode OpenCloudBackupWorkStorageFileSystem(ServiceCtx context) + { + CloudBackupWorkStorageId storageId = (CloudBackupWorkStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenCloudBackupWorkStorageFileSystem(ref fileSystem.Ref, storageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(130)] + public ResultCode OpenCustomStorageFileSystem(ServiceCtx context) + { + CustomStorageId customStorageId = (CustomStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenCustomStorageFileSystem(ref fileSystem.Ref, customStorageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(200)] + // OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage + public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context) + { + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage); + using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(202)] + // OpenDataStorageByDataId(u8 storageId, nn::ncm::DataId dataId) -> object<nn::fssrv::sf::IStorage> dataStorage + public ResultCode OpenDataStorageByDataId(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); + byte[] padding = context.RequestData.ReadBytes(7); + ulong titleId = context.RequestData.ReadUInt64(); + + // We do a mitm here to find if the request is for an AOC. + // This is because AOC can be distributed over multiple containers in the emulator. + if (context.Device.System.ContentManager.GetAocDataStorage(titleId, out LibHac.Fs.IStorage aocStorage, context.Device.Configuration.FsIntegrityCheckLevel)) + { + Logger.Info?.Print(LogClass.Loader, $"Opened AddOnContent Data TitleID={titleId:X16}"); + + var storage = context.Device.FileSystem.ModLoader.ApplyRomFsMods(titleId, aocStorage); + using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage); + using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + NcaContentType contentType = NcaContentType.Data; + + StorageId installedStorage = context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + + if (installedStorage == StorageId.None) + { + contentType = NcaContentType.PublicData; + + installedStorage = context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + } + + if (installedStorage != StorageId.None) + { + string contentPath = context.Device.System.ContentManager.GetInstalledContentPath(titleId, storageId, contentType); + string installPath = context.Device.FileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(installPath)) + { + string ncaPath = installPath; + + if (File.Exists(ncaPath)) + { + try + { + LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + Nca nca = new Nca(context.Device.System.KeySet, ncaStorage); + LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(romfsStorage); + using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + else + { + throw new FileNotFoundException($"No Nca found in Path `{ncaPath}`."); + } + } + else + { + throw new DirectoryNotFoundException($"Path for title id {titleId:x16} on Storage {storageId} was not found in Path {installPath}."); + } + } + + throw new FileNotFoundException($"System archive with titleid {titleId:x16} was not found on Storage {storageId}. Found in {installedStorage}."); + } + + [CommandCmif(203)] + // OpenPatchDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> + public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context) + { + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage); + using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(205)] + // OpenDataStorageWithProgramIndex(u8 program_index) -> object<nn::fssrv::sf::IStorage> + public ResultCode OpenDataStorageWithProgramIndex(ServiceCtx context) + { + byte programIndex = context.RequestData.ReadByte(); + + if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex) + { + throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); + } + + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage); + using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(400)] + // OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage + public ResultCode OpenDeviceOperator(ServiceCtx context) + { + using var deviceOperator = new SharedRef<LibHac.FsSrv.Sf.IDeviceOperator>(); + + Result result = _baseFileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new IDeviceOperator(ref deviceOperator.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(601)] + public ResultCode QuerySaveDataTotalSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + Result result = _baseFileSystemProxy.Get.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandCmif(511)] + public ResultCode NotifySystemDataUpdateEvent(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.NotifySystemDataUpdateEvent().Value; + } + + [CommandCmif(523)] + public ResultCode SimulateDeviceDetectionEvent(ServiceCtx context) + { + bool signalEvent = context.RequestData.ReadBoolean(); + context.RequestData.BaseStream.Seek(3, SeekOrigin.Current); + SdmmcPort port = context.RequestData.ReadStruct<SdmmcPort>(); + SimulatingDeviceDetectionMode mode = context.RequestData.ReadStruct<SimulatingDeviceDetectionMode>(); + + return (ResultCode)_baseFileSystemProxy.Get.SimulateDeviceDetectionEvent(port, mode, signalEvent).Value; + } + + [CommandCmif(602)] + public ResultCode VerifySaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.VerifySaveDataFileSystem(saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandCmif(603)] + public ResultCode CorruptSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystem(saveDataId).Value; + } + + [CommandCmif(604)] + public ResultCode CreatePaddingFile(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CreatePaddingFile(size).Value; + } + + [CommandCmif(605)] + public ResultCode DeleteAllPaddingFiles(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.DeleteAllPaddingFiles().Value; + } + + [CommandCmif(606)] + public ResultCode GetRightsId(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadInt64(); + ProgramId programId = context.RequestData.ReadStruct<ProgramId>(); + + Result result = _baseFileSystemProxy.Get.GetRightsId(out RightsId rightsId, programId, storageId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(607)] + public ResultCode RegisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct<RightsId>(); + AccessKey accessKey = context.RequestData.ReadStruct<AccessKey>(); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterExternalKey(in rightsId, in accessKey).Value; + } + + [CommandCmif(608)] + public ResultCode UnregisterAllExternalKey(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.UnregisterAllExternalKey().Value; + } + + [CommandCmif(609)] + public ResultCode GetRightsIdByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Get.GetRightsIdByPath(out RightsId rightsId, in path); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(610)] + public ResultCode GetRightsIdAndKeyGenerationByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in path); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(keyGeneration); + context.ResponseData.BaseStream.Seek(7, SeekOrigin.Current); + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(611)] + public ResultCode SetCurrentPosixTimeWithTimeDifference(ServiceCtx context) + { + int timeDifference = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); + long time = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.SetCurrentPosixTimeWithTimeDifference(time, timeDifference).Value; + } + + [CommandCmif(612)] + public ResultCode GetFreeSpaceSizeForSaveData(ServiceCtx context) + { + SaveDataSpaceId spaceId = context.RequestData.ReadStruct<SaveDataSpaceId>(); + + Result result = _baseFileSystemProxy.Get.GetFreeSpaceSizeForSaveData(out long freeSpaceSize, spaceId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(freeSpaceSize); + + return ResultCode.Success; + } + + [CommandCmif(613)] + public ResultCode VerifySaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandCmif(614)] + public ResultCode CorruptSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } + + [CommandCmif(615)] + public ResultCode QuerySaveDataInternalStorageTotalSize(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.QuerySaveDataInternalStorageTotalSize(out long size, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(size); + + return ResultCode.Success; + } + + [CommandCmif(616)] + public ResultCode GetSaveDataCommitId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.GetSaveDataCommitId(out long commitId, spaceId, saveDataId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(commitId); + + return ResultCode.Success; + } + + [CommandCmif(617)] + public ResultCode UnregisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct<RightsId>(); + + return (ResultCode)_baseFileSystemProxy.Get.UnregisterExternalKey(in rightsId).Value; + } + + [CommandCmif(620)] + public ResultCode SetSdCardEncryptionSeed(ServiceCtx context) + { + EncryptionSeed encryptionSeed = context.RequestData.ReadStruct<EncryptionSeed>(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSdCardEncryptionSeed(in encryptionSeed).Value; + } + + [CommandCmif(630)] + // SetSdCardAccessibility(u8 isAccessible) + public ResultCode SetSdCardAccessibility(ServiceCtx context) + { + bool isAccessible = context.RequestData.ReadBoolean(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSdCardAccessibility(isAccessible).Value; + } + + [CommandCmif(631)] + // IsSdCardAccessible() -> u8 isAccessible + public ResultCode IsSdCardAccessible(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.IsSdCardAccessible(out bool isAccessible); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(isAccessible); + + return ResultCode.Success; + } + + [CommandCmif(702)] + public ResultCode IsAccessFailureDetected(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.IsAccessFailureDetected(out bool isDetected, processId); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(isDetected); + + return ResultCode.Success; + } + + [CommandCmif(710)] + public ResultCode ResolveAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.ResolveAccessFailure(processId).Value; + } + + [CommandCmif(720)] + public ResultCode AbandonAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.AbandonAccessFailure(processId).Value; + } + + [CommandCmif(800)] + public ResultCode GetAndClearErrorInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(errorInfo); + + return ResultCode.Success; + } + + [CommandCmif(810)] + public ResultCode RegisterProgramIndexMapInfo(ServiceCtx context) + { + int programCount = context.RequestData.ReadInt32(); + + byte[] mapInfoBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, mapInfoBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterProgramIndexMapInfo(new InBuffer(mapInfoBuffer), programCount).Value; + } + + [CommandCmif(1000)] + public ResultCode SetBisRootForHost(ServiceCtx context) + { + BisPartitionId partitionId = (BisPartitionId)context.RequestData.ReadInt32(); + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Get.SetBisRootForHost(partitionId, in path).Value; + } + + [CommandCmif(1001)] + public ResultCode SetSaveDataSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSaveDataSize(dataSize, journalSize).Value; + } + + [CommandCmif(1002)] + public ResultCode SetSaveDataRootPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Get.SetSaveDataRootPath(in path).Value; + } + + [CommandCmif(1003)] + public ResultCode DisableAutoSaveDataCreation(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.DisableAutoSaveDataCreation().Value; + } + + [CommandCmif(1004)] + // SetGlobalAccessLogMode(u32 mode) + public ResultCode SetGlobalAccessLogMode(ServiceCtx context) + { + int mode = context.RequestData.ReadInt32(); + + context.Device.System.GlobalAccessLogMode = mode; + + return ResultCode.Success; + } + + [CommandCmif(1005)] + // GetGlobalAccessLogMode() -> u32 logMode + public ResultCode GetGlobalAccessLogMode(ServiceCtx context) + { + int mode = context.Device.System.GlobalAccessLogMode; + + context.ResponseData.Write(mode); + + return ResultCode.Success; + } + + [CommandCmif(1006)] + // OutputAccessLogToSdCard(buffer<bytes, 5> log_text) + public ResultCode OutputAccessLogToSdCard(ServiceCtx context) + { + string message = ReadUtf8StringSend(context); + + // FS ends each line with a newline. Remove it because Ryujinx logging adds its own newline + Logger.AccessLog?.PrintMsg(LogClass.ServiceFs, message.TrimEnd('\n')); + + return ResultCode.Success; + } + + [CommandCmif(1007)] + public ResultCode RegisterUpdatePartition(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.RegisterUpdatePartition().Value; + } + + [CommandCmif(1008)] + public ResultCode OpenRegisteredUpdatePartition(ServiceCtx context) + { + using var fileSystem = new SharedRef<IFileSystem>(); + + Result result = _baseFileSystemProxy.Get.OpenRegisteredUpdatePartition(ref fileSystem.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(1009)] + public ResultCode GetAndClearMemoryReportInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.WriteStruct(reportInfo); + + return ResultCode.Success; + } + + [CommandCmif(1011)] + public ResultCode GetProgramIndexForAccessLog(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetProgramIndexForAccessLog(out int programIndex, out int programCount); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(programIndex); + context.ResponseData.Write(programCount); + + return ResultCode.Success; + } + + [CommandCmif(1012)] + public ResultCode GetFsStackUsage(ServiceCtx context) + { + FsStackUsageThreadType threadType = context.RequestData.ReadStruct<FsStackUsageThreadType>(); + + Result result = _baseFileSystemProxy.Get.GetFsStackUsage(out uint usage, threadType); + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write(usage); + + return ResultCode.Success; + } + + [CommandCmif(1013)] + public ResultCode UnsetSaveDataRootPath(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.UnsetSaveDataRootPath().Value; + } + + [CommandCmif(1014)] + public ResultCode OutputMultiProgramTagAccessLog(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.OutputMultiProgramTagAccessLog().Value; + } + + [CommandCmif(1016)] + public ResultCode FlushAccessLogOnSdCard(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value; + } + + [CommandCmif(1017)] + public ResultCode OutputApplicationInfoAccessLog(ServiceCtx context) + { + ApplicationInfo info = context.RequestData.ReadStruct<ApplicationInfo>(); + + return (ResultCode)_baseFileSystemProxy.Get.OutputApplicationInfoAccessLog(in info).Value; + } + + [CommandCmif(1100)] + public ResultCode OverrideSaveDataTransferTokenSignVerificationKey(ServiceCtx context) + { + byte[] keyBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, keyBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.OverrideSaveDataTransferTokenSignVerificationKey(new InBuffer(keyBuffer)).Value; + } + + [CommandCmif(1110)] + public ResultCode CorruptSaveDataFileSystemByOffset(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long offset = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset).Value; + } + + [CommandCmif(1200)] // 6.0.0+ + // OpenMultiCommitManager() -> object<nn::fssrv::sf::IMultiCommitManager> + public ResultCode OpenMultiCommitManager(ServiceCtx context) + { + using var commitManager = new SharedRef<LibHac.FsSrv.Sf.IMultiCommitManager>(); + + Result result = _baseFileSystemProxy.Get.OpenMultiCommitManager(ref commitManager.Ref); + if (result.IsFailure()) return (ResultCode)result.Value; + + MakeObject(context, new IMultiCommitManager(ref commitManager.Ref)); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseFileSystemProxy.Destroy(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs new file mode 100644 index 00000000..a40821b9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-ldr")] + class IFileSystemProxyForLoader : IpcService + { + public IFileSystemProxyForLoader(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs new file mode 100644 index 00000000..aa04a7e6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs @@ -0,0 +1,44 @@ +using LibHac; +using LibHac.Common; +using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class IMultiCommitManager : DisposableIpcService // 6.0.0+ + { + private SharedRef<LibHac.FsSrv.Sf.IMultiCommitManager> _baseCommitManager; + + public IMultiCommitManager(ref SharedRef<LibHac.FsSrv.Sf.IMultiCommitManager> baseCommitManager) + { + _baseCommitManager = SharedRef<LibHac.FsSrv.Sf.IMultiCommitManager>.CreateMove(ref baseCommitManager); + } + + [CommandCmif(1)] // 6.0.0+ + // Add(object<nn::fssrv::sf::IFileSystem>) + public ResultCode Add(ServiceCtx context) + { + using SharedRef<LibHac.FsSrv.Sf.IFileSystem> fileSystem = GetObject<IFileSystem>(context, 0).GetBaseFileSystem(); + + Result result = _baseCommitManager.Get.Add(ref fileSystem.Ref); + + return (ResultCode)result.Value; + } + + [CommandCmif(2)] // 6.0.0+ + // Commit() + public ResultCode Commit(ServiceCtx context) + { + Result result = _baseCommitManager.Get.Commit(); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseCommitManager.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs new file mode 100644 index 00000000..e11eadf5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-pr")] + class IProgramRegistry : IpcService + { + public IProgramRegistry(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs b/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs new file mode 100644 index 00000000..0611375b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs @@ -0,0 +1,41 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class ISaveDataInfoReader : DisposableIpcService + { + private SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader> _baseReader; + + public ISaveDataInfoReader(ref SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader> baseReader) + { + _baseReader = SharedRef<LibHac.FsSrv.Sf.ISaveDataInfoReader>.CreateMove(ref baseReader); + } + + [CommandCmif(0)] + // ReadSaveDataInfo() -> (u64, buffer<unknown, 6>) + public ResultCode ReadSaveDataInfo(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Result result = _baseReader.Get.Read(out long readCount, new OutBuffer(region.Memory.Span)); + + context.ResponseData.Write(readCount); + + return (ResultCode)result.Value; + } + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseReader.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs new file mode 100644 index 00000000..8f87142b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum ResultCode + { + ModuleId = 2, + ErrorCodeShift = 9, + + Success = 0, + + PathDoesNotExist = (1 << ErrorCodeShift) | ModuleId, + PathAlreadyExists = (2 << ErrorCodeShift) | ModuleId, + PathAlreadyInUse = (7 << ErrorCodeShift) | ModuleId, + PartitionNotFound = (1001 << ErrorCodeShift) | ModuleId, + InvalidInput = (6001 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs b/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs new file mode 100644 index 00000000..f12c1661 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum FileSystemType + { + Logo = 2, + ContentControl = 3, + ContentManual = 4, + ContentMeta = 5, + ContentData = 6, + ApplicationPackage = 7 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs b/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs new file mode 100644 index 00000000..90646b40 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:c")] // 4.0.0+ + class IGrcService : IpcService + { + public IGrcService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs b/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs new file mode 100644 index 00000000..edb1d64e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:d")] // 6.0.0+ + class IRemoteVideoTransfer : IpcService + { + public IRemoteVideoTransfer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs new file mode 100644 index 00000000..b1466c78 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs @@ -0,0 +1,107 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class Hid + { + private readonly Switch _device; + + private readonly SharedMemoryStorage _storage; + + internal ref SharedMemory SharedMemory => ref _storage.GetRef<SharedMemory>(0); + + internal const int SharedMemEntryCount = 17; + + public DebugPadDevice DebugPad; + public TouchDevice Touchscreen; + public MouseDevice Mouse; + public KeyboardDevice Keyboard; + public NpadDevices Npads; + + private static void CheckTypeSizeOrThrow<T>(int expectedSize) + { + if (Unsafe.SizeOf<T>() != expectedSize) + { + throw new InvalidStructLayoutException<T>(expectedSize); + } + } + + static Hid() + { + CheckTypeSizeOrThrow<RingLifo<DebugPadState>>(0x2c8); + CheckTypeSizeOrThrow<RingLifo<TouchScreenState>>(0x2C38); + CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350); + CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8); + CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000); + CheckTypeSizeOrThrow<SharedMemory>(Horizon.HidSize); + } + + internal Hid(in Switch device, SharedMemoryStorage storage) + { + _device = device; + _storage = storage; + + SharedMemory = SharedMemory.Create(); + + InitDevices(); + } + + private void InitDevices() + { + DebugPad = new DebugPadDevice(_device, true); + Touchscreen = new TouchDevice(_device, true); + Mouse = new MouseDevice(_device, false); + Keyboard = new KeyboardDevice(_device, false); + Npads = new NpadDevices(_device, true); + } + + public void RefreshInputConfig(List<InputConfig> inputConfig) + { + ControllerConfig[] npadConfig = new ControllerConfig[inputConfig.Count]; + + for (int i = 0; i < npadConfig.Length; ++i) + { + npadConfig[i].Player = (PlayerIndex)inputConfig[i].PlayerIndex; + npadConfig[i].Type = (ControllerType)inputConfig[i].ControllerType; + } + + _device.Hid.Npads.Configure(npadConfig); + } + + public ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick) + { + const int stickButtonThreshold = short.MaxValue / 2; + ControllerKeys result = 0; + + result |= (leftStick.Dx < -stickButtonThreshold) ? ControllerKeys.LStickLeft : result; + result |= (leftStick.Dx > stickButtonThreshold) ? ControllerKeys.LStickRight : result; + result |= (leftStick.Dy < -stickButtonThreshold) ? ControllerKeys.LStickDown : result; + result |= (leftStick.Dy > stickButtonThreshold) ? ControllerKeys.LStickUp : result; + + result |= (rightStick.Dx < -stickButtonThreshold) ? ControllerKeys.RStickLeft : result; + result |= (rightStick.Dx > stickButtonThreshold) ? ControllerKeys.RStickRight : result; + result |= (rightStick.Dy < -stickButtonThreshold) ? ControllerKeys.RStickDown : result; + result |= (rightStick.Dy > stickButtonThreshold) ? ControllerKeys.RStickUp : result; + + return result; + } + + internal static ulong GetTimestampTicks() + { + return (ulong)PerformanceCounter.ElapsedMilliseconds * 19200; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs new file mode 100644 index 00000000..0e3cd18a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public abstract class BaseDevice + { + protected readonly Switch _device; + public bool Active; + + public BaseDevice(Switch device, bool active) + { + _device = device; + Active = active; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs new file mode 100644 index 00000000..e3b95390 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class DebugPadDevice : BaseDevice + { + public DebugPadDevice(Switch device, bool active) : base(device, active) { } + + public void Update() + { + ref RingLifo<DebugPadState> lifo = ref _device.Hid.SharedMemory.DebugPad; + + ref DebugPadState previousEntry = ref lifo.GetCurrentEntryRef(); + + DebugPadState newState = new DebugPadState(); + + if (Active) + { + // TODO: This is a debug device only present in dev environment, do we want to support it? + } + + newState.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref newState); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs new file mode 100644 index 00000000..8908b74d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class KeyboardDevice : BaseDevice + { + public KeyboardDevice(Switch device, bool active) : base(device, active) { } + + public void Update(KeyboardInput keyState) + { + ref RingLifo<KeyboardState> lifo = ref _device.Hid.SharedMemory.Keyboard; + + if (!Active) + { + lifo.Clear(); + + return; + } + + ref KeyboardState previousEntry = ref lifo.GetCurrentEntryRef(); + + KeyboardState newState = new KeyboardState + { + SamplingNumber = previousEntry.SamplingNumber + 1, + }; + + keyState.Keys.AsSpan().CopyTo(newState.Keys.RawData.AsSpan()); + newState.Modifiers = (KeyboardModifier)keyState.Modifier; + + lifo.Write(ref newState); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs new file mode 100644 index 00000000..66d1b0c4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs @@ -0,0 +1,36 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class MouseDevice : BaseDevice + { + public MouseDevice(Switch device, bool active) : base(device, active) { } + + public void Update(int mouseX, int mouseY, uint buttons = 0, int scrollX = 0, int scrollY = 0, bool connected = false) + { + ref RingLifo<MouseState> lifo = ref _device.Hid.SharedMemory.Mouse; + + ref MouseState previousEntry = ref lifo.GetCurrentEntryRef(); + + MouseState newState = new MouseState() + { + SamplingNumber = previousEntry.SamplingNumber + 1, + }; + + if (Active) + { + newState.Buttons = (MouseButton)buttons; + newState.X = mouseX; + newState.Y = mouseY; + newState.DeltaX = mouseX - previousEntry.DeltaX; + newState.DeltaY = mouseY - previousEntry.DeltaY; + newState.WheelDeltaX = scrollX; + newState.WheelDeltaY = scrollY; + newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None; + } + + lifo.Write(ref newState); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs new file mode 100644 index 00000000..edcc47d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -0,0 +1,635 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class NpadDevices : BaseDevice + { + private const int NoMatchNotifyFrequencyMs = 2000; + private int _activeCount; + private long _lastNotifyTimestamp; + + public const int MaxControllers = 9; // Players 1-8 and Handheld + private ControllerType[] _configuredTypes; + private KEvent[] _styleSetUpdateEvents; + private bool[] _supportedPlayers; + private static VibrationValue _neutralVibrationValue = new VibrationValue + { + AmplitudeLow = 0f, + FrequencyLow = 160f, + AmplitudeHigh = 0f, + FrequencyHigh = 320f + }; + + internal NpadJoyHoldType JoyHold { get; set; } + internal bool SixAxisActive = false; // TODO: link to hidserver when implemented + internal ControllerType SupportedStyleSets { get; set; } + + public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>(); + public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>(); + + public NpadDevices(Switch device, bool active = true) : base(device, active) + { + _configuredTypes = new ControllerType[MaxControllers]; + + SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair | + ControllerType.JoyconLeft | ControllerType.JoyconRight | + ControllerType.ProController; + + _supportedPlayers = new bool[MaxControllers]; + _supportedPlayers.AsSpan().Fill(true); + + _styleSetUpdateEvents = new KEvent[MaxControllers]; + for (int i = 0; i < _styleSetUpdateEvents.Length; ++i) + { + _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext); + } + + _activeCount = 0; + + JoyHold = NpadJoyHoldType.Vertical; + } + + internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) + { + return ref _styleSetUpdateEvents[(int)player]; + } + + internal void ClearSupportedPlayers() + { + _supportedPlayers.AsSpan().Clear(); + } + + internal void SetSupportedPlayer(PlayerIndex player, bool supported = true) + { + _supportedPlayers[(int)player] = supported; + } + + internal IEnumerable<PlayerIndex> GetSupportedPlayers() + { + for (int i = 0; i < _supportedPlayers.Length; ++i) + { + if (_supportedPlayers[i]) + { + yield return (PlayerIndex)i; + } + } + } + + public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex) + { + primaryIndex = PlayerIndex.Unknown; + configuredCount = 0; + + for (int i = 0; i < MaxControllers; ++i) + { + ControllerType npad = _configuredTypes[i]; + + if (npad == ControllerType.Handheld && _device.System.State.DockedMode) + { + continue; + } + + ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet; + + if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i]) + { + configuredCount++; + if (primaryIndex == PlayerIndex.Unknown) + { + primaryIndex = (PlayerIndex)i; + } + } + } + + if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown) + { + return false; + } + + return true; + } + + public void Configure(params ControllerConfig[] configs) + { + _configuredTypes = new ControllerType[MaxControllers]; + + for (int i = 0; i < configs.Length; ++i) + { + PlayerIndex player = configs[i].Player; + ControllerType controllerType = configs[i].Type; + + if (player > PlayerIndex.Handheld) + { + throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld"); + } + + if (controllerType == ControllerType.Handheld) + { + player = PlayerIndex.Handheld; + } + + _configuredTypes[(int)player] = controllerType; + + Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}"); + } + } + + public void Update(IList<GamepadInput> states) + { + Remap(); + + Span<bool> updated = stackalloc bool[10]; + + // Update configured inputs + for (int i = 0; i < states.Count; ++i) + { + GamepadInput state = states[i]; + + updated[(int)state.PlayerId] = true; + + UpdateInput(state); + } + + for (int i = 0; i < updated.Length; i++) + { + if (!updated[i]) + { + UpdateDisconnectedInput((PlayerIndex)i); + } + } + } + + private void Remap() + { + // Remap/Init if necessary + for (int i = 0; i < MaxControllers; ++i) + { + ControllerType config = _configuredTypes[i]; + + // Remove Handheld config when Docked + if (config == ControllerType.Handheld && _device.System.State.DockedMode) + { + config = ControllerType.None; + } + + // Auto-remap ProController and JoyconPair + if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0) + { + config = ControllerType.ProController; + } + else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0) + { + config = ControllerType.JoyconPair; + } + + // Check StyleSet and PlayerSet + if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i]) + { + config = ControllerType.None; + } + + SetupNpad((PlayerIndex)i, config); + } + + if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs) + { + Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'"); + _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds; + } + } + + private void SetupNpad(PlayerIndex player, ControllerType type) + { + ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState; + + ControllerType oldType = (ControllerType)controller.StyleSet; + + if (oldType == type) + { + return; // Already configured + } + + controller = NpadInternalState.Create(); // Reset it + + if (type == ControllerType.None) + { + _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect + _activeCount--; + + Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}"); + + return; + } + + // TODO: Allow customizing colors at config + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual; + controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray; + controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray; + controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue; + controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray; + controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed; + controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray; + + controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual | + NpadSystemProperties.IsPoweredJoyLeft | + NpadSystemProperties.IsPoweredJoyRight; + + controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100; + controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100; + controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100; + + switch (type) + { + case ControllerType.ProController: + controller.StyleSet = NpadStyleTag.FullKey; + controller.DeviceType = DeviceType.FullKey; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = AppletFooterUiType.SwitchProController; + break; + case ControllerType.Handheld: + controller.StyleSet = NpadStyleTag.Handheld; + controller.DeviceType = DeviceType.HandheldLeft | + DeviceType.HandheldRight; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = AppletFooterUiType.HandheldJoyConLeftJoyConRight; + break; + case ControllerType.JoyconPair: + controller.StyleSet = NpadStyleTag.JoyDual; + controller.DeviceType = DeviceType.JoyLeft | + DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDual : AppletFooterUiType.HandheldJoyConLeftJoyConRight; + break; + case ControllerType.JoyconLeft: + controller.StyleSet = NpadStyleTag.JoyLeft; + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + controller.DeviceType = DeviceType.JoyLeft; + controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualLeftOnly : AppletFooterUiType.HandheldJoyConLeftOnly; + break; + case ControllerType.JoyconRight: + controller.StyleSet = NpadStyleTag.JoyRight; + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + controller.DeviceType = DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented | + NpadSystemProperties.IsPlusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualRightOnly : AppletFooterUiType.HandheldJoyConRightOnly; + break; + case ControllerType.Pokeball: + controller.StyleSet = NpadStyleTag.Palma; + controller.DeviceType = DeviceType.Palma; + controller.AppletFooterUiType = AppletFooterUiType.None; + break; + } + + _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); + _activeCount++; + + Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}"); + } + + private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKey; + case NpadStyleTag.Handheld: + return ref npad.Handheld; + case NpadStyleTag.JoyDual: + return ref npad.JoyDual; + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeft; + case NpadStyleTag.JoyRight: + return ref npad.JoyRight; + case NpadStyleTag.Palma: + return ref npad.Palma; + default: + return ref npad.SystemExt; + } + } + + private void UpdateUnusedInputIfNotEqual(ref RingLifo<NpadCommonState> currentlyUsed, ref RingLifo<NpadCommonState> possiblyUnused) + { + if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused)) + { + NpadCommonState newState = new NpadCommonState(); + + WriteNewInputEntry(ref possiblyUnused, ref newState); + } + } + + private void WriteNewInputEntry(ref RingLifo<NpadCommonState> lifo, ref NpadCommonState state) + { + ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef(); + + state.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref state); + } + + private void UpdateUnusedSixInputIfNotEqual(ref RingLifo<SixAxisSensorState> currentlyUsed, ref RingLifo<SixAxisSensorState> possiblyUnused) + { + if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused)) + { + SixAxisSensorState newState = new SixAxisSensorState(); + + WriteNewSixInputEntry(ref possiblyUnused, ref newState); + } + } + + private void WriteNewSixInputEntry(ref RingLifo<SixAxisSensorState> lifo, ref SixAxisSensorState state) + { + ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef(); + + state.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref state); + } + + private void UpdateInput(GamepadInput state) + { + if (state.PlayerId == PlayerIndex.Unknown) + { + return; + } + + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState; + + if (currentNpad.StyleSet == NpadStyleTag.None) + { + return; + } + + ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad); + + NpadCommonState newState = new NpadCommonState + { + Buttons = (NpadButton)state.Buttons, + AnalogStickL = new AnalogStickState + { + X = state.LStick.Dx, + Y = state.LStick.Dy, + }, + AnalogStickR = new AnalogStickState + { + X = state.RStick.Dx, + Y = state.RStick.Dy, + } + }; + + newState.Attributes = NpadAttribute.IsConnected; + + switch (currentNpad.StyleSet) + { + case NpadStyleTag.Handheld: + case NpadStyleTag.FullKey: + newState.Attributes |= NpadAttribute.IsWired; + break; + case NpadStyleTag.JoyDual: + newState.Attributes |= NpadAttribute.IsLeftConnected | + NpadAttribute.IsRightConnected; + break; + case NpadStyleTag.JoyLeft: + newState.Attributes |= NpadAttribute.IsLeftConnected; + break; + case NpadStyleTag.JoyRight: + newState.Attributes |= NpadAttribute.IsRightConnected; + break; + } + + WriteNewInputEntry(ref lifo, ref newState); + + // Mirror data to Default layout just in case + if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt)) + { + WriteNewInputEntry(ref currentNpad.SystemExt, ref newState); + } + + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma); + } + + private void UpdateDisconnectedInput(PlayerIndex index) + { + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState; + + NpadCommonState newState = new NpadCommonState(); + + WriteNewInputEntry(ref currentNpad.FullKey, ref newState); + WriteNewInputEntry(ref currentNpad.Handheld, ref newState); + WriteNewInputEntry(ref currentNpad.JoyDual, ref newState); + WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState); + WriteNewInputEntry(ref currentNpad.JoyRight, ref newState); + WriteNewInputEntry(ref currentNpad.Palma, ref newState); + } + + public void UpdateSixAxis(IList<SixAxisInput> states) + { + Span<bool> updated = stackalloc bool[10]; + + for (int i = 0; i < states.Count; ++i) + { + updated[(int)states[i].PlayerId] = true; + + if (SetSixAxisState(states[i])) + { + i++; + + if (i >= states.Count) + { + return; + } + + SetSixAxisState(states[i], true); + } + } + + for (int i = 0; i < updated.Length; i++) + { + if (!updated[i]) + { + UpdateDisconnectedInputSixAxis((PlayerIndex)i); + } + } + } + + private ref RingLifo<SixAxisSensorState> GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKeySixAxisSensor; + case NpadStyleTag.Handheld: + return ref npad.HandheldSixAxisSensor; + case NpadStyleTag.JoyDual: + if (isRightPair) + { + return ref npad.JoyDualRightSixAxisSensor; + } + else + { + return ref npad.JoyDualSixAxisSensor; + } + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeftSixAxisSensor; + case NpadStyleTag.JoyRight: + return ref npad.JoyRightSixAxisSensor; + default: + throw new NotImplementedException($"{npad.StyleSet}"); + } + } + + private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false) + { + if (state.PlayerId == PlayerIndex.Unknown) + { + return false; + } + + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState; + + if (currentNpad.StyleSet == NpadStyleTag.None) + { + return false; + } + + HidVector accel = new HidVector() + { + X = state.Accelerometer.X, + Y = state.Accelerometer.Y, + Z = state.Accelerometer.Z + }; + + HidVector gyro = new HidVector() + { + X = state.Gyroscope.X, + Y = state.Gyroscope.Y, + Z = state.Gyroscope.Z + }; + + HidVector rotation = new HidVector() + { + X = state.Rotation.X, + Y = state.Rotation.Y, + Z = state.Rotation.Z + }; + + SixAxisSensorState newState = new SixAxisSensorState + { + Acceleration = accel, + AngularVelocity = gyro, + Angle = rotation, + Attributes = SixAxisSensorAttribute.IsConnected + }; + + state.Orientation.AsSpan().CopyTo(newState.Direction.AsSpan()); + + ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair); + + WriteNewSixInputEntry(ref lifo, ref newState); + + bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair; + + if (!isRightPair) + { + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor); + } + + if (!needUpdateRight && !isRightPair) + { + SixAxisSensorState emptyState = new SixAxisSensorState(); + + emptyState.Attributes = SixAxisSensorAttribute.IsConnected; + + WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState); + } + + return needUpdateRight; + } + + private void UpdateDisconnectedInputSixAxis(PlayerIndex index) + { + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState; + + SixAxisSensorState newState = new SixAxisSensorState(); + + newState.Attributes = SixAxisSensorAttribute.IsConnected; + + WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState); + } + + public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues) + { + if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue)) + { + if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue)) + { + leftVibrationValue = _neutralVibrationValue; + } + + if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue)) + { + rightVibrationValue = _neutralVibrationValue; + } + + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) + { + currentQueue.Enqueue((leftVibrationValue, rightVibrationValue)); + + LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue); + } + } + } + + public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position) + { + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue)) + { + return _neutralVibrationValue; + } + + return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2; + } + + public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index) + { + if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue)) + { + rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>(); + _device.Hid.Npads.RumbleQueues[index] = rumbleQueue; + } + + return rumbleQueue; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs new file mode 100644 index 00000000..bb58ee51 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs @@ -0,0 +1,48 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class TouchDevice : BaseDevice + { + public TouchDevice(Switch device, bool active) : base(device, active) { } + + public void Update(params TouchPoint[] points) + { + ref RingLifo<TouchScreenState> lifo = ref _device.Hid.SharedMemory.TouchScreen; + + ref TouchScreenState previousEntry = ref lifo.GetCurrentEntryRef(); + + TouchScreenState newState = new TouchScreenState + { + SamplingNumber = previousEntry.SamplingNumber + 1 + }; + + if (Active) + { + newState.TouchesCount = points.Length; + + int pointsLength = Math.Min(points.Length, newState.Touches.Length); + + for (int i = 0; i < pointsLength; ++i) + { + TouchPoint pi = points[i]; + newState.Touches[i] = new TouchState + { + DeltaTime = newState.SamplingNumber, + Attribute = pi.Attribute, + X = pi.X, + Y = pi.Y, + FingerId = (uint)i, + DiameterX = pi.DiameterX, + DiameterY = pi.DiameterY, + RotationAngle = pi.Angle + }; + } + } + + lifo.Write(ref newState); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs new file mode 100644 index 00000000..477e1a84 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct ControllerConfig + { + public PlayerIndex Player; + public ControllerType Type; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs new file mode 100644 index 00000000..633671df --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct GamepadInput + { + public PlayerIndex PlayerId; + public ControllerKeys Buttons; + public JoystickPosition LStick; + public JoystickPosition RStick; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs new file mode 100644 index 00000000..6df477d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct JoystickPosition + { + public int Dx; + public int Dy; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs new file mode 100644 index 00000000..be6857fb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct KeyboardInput + { + public int Modifier; + public ulong[] Keys; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs new file mode 100644 index 00000000..4dda82c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs @@ -0,0 +1,13 @@ +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct SixAxisInput + { + public PlayerIndex PlayerId; + public Vector3 Accelerometer; + public Vector3 Gyroscope; + public Vector3 Rotation; + public float[] Orientation; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs new file mode 100644 index 00000000..457d2b0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct TouchPoint + { + public TouchAttribute Attribute; + public uint X; + public uint Y; + public uint DiameterX; + public uint DiameterY; + public uint Angle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs new file mode 100644 index 00000000..b98f6065 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs @@ -0,0 +1,46 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + static class HidUtils + { + public static PlayerIndex GetIndexFromNpadIdType(NpadIdType npadIdType) + => npadIdType switch + { + NpadIdType.Player1 => PlayerIndex.Player1, + NpadIdType.Player2 => PlayerIndex.Player2, + NpadIdType.Player3 => PlayerIndex.Player3, + NpadIdType.Player4 => PlayerIndex.Player4, + NpadIdType.Player5 => PlayerIndex.Player5, + NpadIdType.Player6 => PlayerIndex.Player6, + NpadIdType.Player7 => PlayerIndex.Player7, + NpadIdType.Player8 => PlayerIndex.Player8, + NpadIdType.Handheld => PlayerIndex.Handheld, + NpadIdType.Unknown => PlayerIndex.Unknown, + _ => throw new ArgumentOutOfRangeException(nameof(npadIdType)) + }; + + public static NpadIdType GetNpadIdTypeFromIndex(PlayerIndex index) + => index switch + { + PlayerIndex.Player1 => NpadIdType.Player1, + PlayerIndex.Player2 => NpadIdType.Player2, + PlayerIndex.Player3 => NpadIdType.Player3, + PlayerIndex.Player4 => NpadIdType.Player4, + PlayerIndex.Player5 => NpadIdType.Player5, + PlayerIndex.Player6 => NpadIdType.Player6, + PlayerIndex.Player7 => NpadIdType.Player7, + PlayerIndex.Player8 => NpadIdType.Player8, + PlayerIndex.Handheld => NpadIdType.Handheld, + PlayerIndex.Unknown => NpadIdType.Unknown, + _ => throw new ArgumentOutOfRangeException(nameof(index)) + }; + + public static bool IsValidNpadIdType(NpadIdType npadIdType) + { + return (npadIdType >= NpadIdType.Player1 && npadIdType <= NpadIdType.Player8) || + npadIdType == NpadIdType.Handheld || + npadIdType == NpadIdType.Unknown; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs new file mode 100644 index 00000000..56f63e52 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IActiveApplicationDeviceList : IpcService + { + public IActiveApplicationDeviceList() { } + + [CommandCmif(0)] + // ActivateVibrationDevice(nn::hid::VibrationDeviceHandle) + public ResultCode ActivateVibrationDevice(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs new file mode 100644 index 00000000..f0aaf5e3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IAppletResource : IpcService + { + private KSharedMemory _hidSharedMem; + private int _hidSharedMemHandle; + + public IAppletResource(KSharedMemory hidSharedMem) + { + _hidSharedMem = hidSharedMem; + } + + [CommandCmif(0)] + // GetSharedMemoryHandle() -> handle<copy> + public ResultCode GetSharedMemoryHandle(ServiceCtx context) + { + if (_hidSharedMemHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_hidSharedMem, out _hidSharedMemHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_hidSharedMemHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs new file mode 100644 index 00000000..0cf4a047 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadHandheldActivationMode + { + Dual, + Single, + None + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs new file mode 100644 index 00000000..05587bfd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadJoyDeviceType + { + Left, + Right + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs new file mode 100644 index 00000000..4fd0a1b5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct AccelerometerParameters + { + public float X; + public float Y; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs new file mode 100644 index 00000000..db7467fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum GyroscopeZeroDriftMode + { + Loose, + Standard, + Tight + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs new file mode 100644 index 00000000..2683ffee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct SensorFusionParameters + { + public float RevisePower; + public float ReviseRange; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs new file mode 100644 index 00000000..fe50e671 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationDeviceHandle + { + public byte DeviceType; + public byte PlayerId; + public byte Position; + public byte Reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs new file mode 100644 index 00000000..117451f1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum VibrationDevicePosition + { + None, + Left, + Right + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs new file mode 100644 index 00000000..4e5557c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum VibrationDeviceType + { + None, + LinearResonantActuator, + GcErm + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs new file mode 100644 index 00000000..91a23eb7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationDeviceValue + { + public VibrationDeviceType DeviceType; + public VibrationDevicePosition Position; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs new file mode 100644 index 00000000..38ac9cca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationValue + { + public float AmplitudeLow; + public float FrequencyLow; + public float AmplitudeHigh; + public float FrequencyHigh; + + public override bool Equals(object obj) + { + return obj is VibrationValue value && + AmplitudeLow == value.AmplitudeLow && + AmplitudeHigh == value.AmplitudeHigh; + } + + public override int GetHashCode() + { + return HashCode.Combine(AmplitudeLow, AmplitudeHigh); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs new file mode 100644 index 00000000..adaaa012 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:dbg")] + class IHidDebugServer : IpcService + { + public IHidDebugServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs new file mode 100644 index 00000000..d508aba4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -0,0 +1,1800 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid")] + class IHidServer : IpcService + { + private KEvent _xpadIdEvent; + private KEvent _palmaOperationCompleteEvent; + + private int _xpadIdEventHandle; + + private bool _sixAxisSensorFusionEnabled; + private bool _unintendedHomeButtonInputProtectionEnabled; + private bool _vibrationPermitted; + private bool _usbFullKeyControllerEnabled; + private bool _isFirmwareUpdateAvailableForSixAxisSensor; + private bool _isSixAxisSensorUnalteredPassthroughEnabled; + + private NpadHandheldActivationMode _npadHandheldActivationMode; + private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode; + + private long _npadCommunicationMode; + private uint _accelerometerPlayMode; +#pragma warning disable CS0649 + private long _vibrationGcErmCommand; +#pragma warning restore CS0649 + private float _sevenSixAxisSensorFusionStrength; + + private SensorFusionParameters _sensorFusionParams; + private AccelerometerParameters _accelerometerParams; + + public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer) + { + _xpadIdEvent = new KEvent(context.Device.System.KernelContext); + _palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext); + + _npadHandheldActivationMode = NpadHandheldActivationMode.Dual; + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; + + _isFirmwareUpdateAvailableForSixAxisSensor = false; + + _sensorFusionParams = new SensorFusionParameters(); + _accelerometerParams = new AccelerometerParameters(); + + // TODO: signal event at right place + _xpadIdEvent.ReadableEvent.Signal(); + + _vibrationPermitted = true; + } + + [CommandCmif(0)] + // CreateAppletResource(nn::applet::AppletResourceUserId) -> object<nn::hid::IAppletResource> + public ResultCode CreateAppletResource(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + MakeObject(context, new IAppletResource(context.Device.System.HidSharedMem)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // ActivateDebugPad(nn::applet::AppletResourceUserId) + public ResultCode ActivateDebugPad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.DebugPad.Update(); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ActivateTouchScreen(nn::applet::AppletResourceUserId) + public ResultCode ActivateTouchScreen(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Touchscreen.Active = true; + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Touchscreen.Update(); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // ActivateMouse(nn::applet::AppletResourceUserId) + public ResultCode ActivateMouse(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Mouse.Active = true; + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Mouse.Update(0, 0); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // ActivateKeyboard(nn::applet::AppletResourceUserId) + public ResultCode ActivateKeyboard(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Keyboard.Active = true; + + // Initialize entries to avoid issues with some games. + + KeyboardInput emptyInput = new KeyboardInput(); + emptyInput.Keys = new ulong[4]; + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Keyboard.Update(emptyInput); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(32)] + // SendKeyboardLockKeyEvent(uint flags, pid) + public ResultCode SendKeyboardLockKeyEvent(ServiceCtx context) + { + uint flags = context.RequestData.ReadUInt32(); + + // NOTE: This signal the keyboard driver about lock events. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { flags }); + + return ResultCode.Success; + } + + [CommandCmif(40)] + // AcquireXpadIdEventHandle(ulong XpadId) -> nn::sf::NativeHandle + public ResultCode AcquireXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + if (context.Process.HandleTable.GenerateHandle(_xpadIdEvent.ReadableEvent, out _xpadIdEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_xpadIdEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [CommandCmif(41)] + // ReleaseXpadIdEventHandle(ulong XpadId) + public ResultCode ReleaseXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + context.Process.HandleTable.CloseHandle(_xpadIdEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [CommandCmif(51)] + // ActivateXpad(nn::hid::BasicXpadId, nn::applet::AppletResourceUserId) + public ResultCode ActivateXpad(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(55)] + // GetXpadIds() -> long IdsCount, buffer<array<nn::hid::BasicXpadId>, type: 0xa> + public ResultCode GetXpadIds(ServiceCtx context) + { + // There is any Xpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(56)] + // ActivateJoyXpad(nn::hid::JoyXpadId) + public ResultCode ActivateJoyXpad(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(58)] + // GetJoyXpadLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoyXpadLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(59)] + // GetJoyXpadIds() -> long IdsCount, buffer<array<nn::hid::JoyXpadId>, type: 0xa> + public ResultCode GetJoyXpadIds(ServiceCtx context) + { + // There is any JoyXpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(60)] + // ActivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode ActivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // DeactivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode DeactivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // GetSixAxisSensorLifoHandle(nn::hid::BasicXpadId) -> nn::sf::NativeHandle + public ResultCode GetSixAxisSensorLifoHandle(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(63)] + // ActivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode ActivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(64)] + // DeactivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode DeactivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // GetJoySixAxisSensorLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoySixAxisSensorLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(66)] + // StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(67)] + // StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(68)] + // IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sixAxisSensorFusionEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(69)] + // EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode EnableSixAxisSensorFusion(ServiceCtx context) + { + _sixAxisSensorFusionEnabled = context.RequestData.ReadUInt32() != 0; + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(70)] + // SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId) + public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + + _sensorFusionParams = new SensorFusionParameters + { + RevisePower = context.RequestData.ReadInt32(), + ReviseRange = context.RequestData.ReadInt32() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(71)] + // GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange) + public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sensorFusionParams.RevisePower); + context.ResponseData.Write(_sensorFusionParams.ReviseRange); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(72)] + // ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _sensorFusionParams.RevisePower = 0; + _sensorFusionParams.ReviseRange = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(73)] + // SetAccelerometerParameters(nn::hid::SixAxisSensorHandle, float X, float Y, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + + _accelerometerParams = new AccelerometerParameters + { + X = context.RequestData.ReadInt32(), + Y = context.RequestData.ReadInt32() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(74)] + // GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y + public ResultCode GetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerParams.X); + context.ResponseData.Write(_accelerometerParams.Y); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(75)] + // ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerParams.X = 0; + _accelerometerParams.Y = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(76)] + // SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + _accelerometerPlayMode = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(77)] + // GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode + public ResultCode GetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerPlayMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(78)] + // ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerPlayMode = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(79)] + // SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId) + public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + _gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(80)] + // GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode + public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)_gyroscopeZeroDriftMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(81)] + // ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(82)] + // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest + public ResultCode IsSixAxisSensorAtRest(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + bool isAtRest = true; + + context.ResponseData.Write(isAtRest); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest }); + + return ResultCode.Success; + } + + [CommandCmif(83)] // 6.0.0+ + // IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable + public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor }); + + return ResultCode.Success; + } + + [CommandCmif(84)] // 13.0.0+ + // EnableSixAxisSensorUnalteredPassthrough(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u8 enabled) + public ResultCode EnableSixAxisSensorUnalteredPassthrough(ServiceCtx context) + { + _isSixAxisSensorUnalteredPassthroughEnabled = context.RequestData.ReadUInt32() != 0; + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isSixAxisSensorUnalteredPassthroughEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(85)] // 13.0.0+ + // IsSixAxisSensorUnalteredPassthroughEnabled(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u8 enabled + public ResultCode IsSixAxisSensorUnalteredPassthroughEnabled(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_isSixAxisSensorUnalteredPassthroughEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(87)] // 13.0.0+ + // LoadSixAxisSensorCalibrationParameter(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u64 unknown) + public ResultCode LoadSixAxisSensorCalibrationParameter(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + // TODO: CalibrationParameter have to be determined. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(88)] // 13.0.0+ + // GetSixAxisSensorIcInformation(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u64 unknown + public ResultCode GetSixAxisSensorIcInformation(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + // TODO: IcInformation have to be determined. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(91)] + // ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0) + public ResultCode ActivateGesture(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int unknown0 = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // SetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag) + public ResultCode SetSupportedNpadStyleSet(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + ControllerType type = (ControllerType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, type }); + + context.Device.Hid.Npads.SupportedStyleSets = type; + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag + public ResultCode GetSupportedNpadStyleSet(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, context.Device.Hid.Npads.SupportedStyleSets }); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array<NpadIdType, 9>) + public ResultCode SetSupportedNpadIdType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + ulong arrayPosition = context.Request.PtrBuff[0].Position; + ulong arraySize = context.Request.PtrBuff[0].Size; + + ReadOnlySpan<NpadIdType> supportedPlayerIds = MemoryMarshal.Cast<byte, NpadIdType>(context.Memory.GetSpan(arrayPosition, (int)arraySize)); + + context.Device.Hid.Npads.ClearSupportedPlayers(); + + for (int i = 0; i < supportedPlayerIds.Length; ++i) + { + if (HidUtils.IsValidNpadIdType(supportedPlayerIds[i])) + { + context.Device.Hid.Npads.SetSupportedPlayer(HidUtils.GetIndexFromNpadIdType(supportedPlayerIds[i])); + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{supportedPlayerIds.Length} Players: " + string.Join(",", supportedPlayerIds.ToArray())); + + return ResultCode.Success; + } + + [CommandCmif(103)] + // ActivateNpad(nn::applet::AppletResourceUserId) + public ResultCode ActivateNpad(ServiceCtx context) + { + return ActiveNpadImpl(context); + } + + [CommandCmif(104)] + // DeactivateNpad(nn::applet::AppletResourceUserId) + public ResultCode DeactivateNpad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Npads.Active = false; + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(106)] + // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle + public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context) + { + PlayerIndex npadId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32()); + long appletResourceUserId = context.RequestData.ReadInt64(); + long npadStyleSet = context.RequestData.ReadInt64(); + + KEvent evnt = context.Device.Hid.Npads.GetStyleSetUpdateEvent(npadId); + if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + // Games expect this event to be signaled after calling this function + evnt.ReadableEvent.Signal(); + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadId, npadStyleSet }); + + return ResultCode.Success; + } + + [CommandCmif(107)] + // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType) + public ResultCode DisconnectNpad(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); + + return ResultCode.Success; + } + + [CommandCmif(108)] + // GetPlayerLedPattern(u32 npad_id) -> u64 led_pattern + public ResultCode GetPlayerLedPattern(ServiceCtx context) + { + NpadIdType npadId = (NpadIdType)context.RequestData.ReadUInt32(); + + ulong ledPattern = npadId switch + { + NpadIdType.Player1 => 0b0001, + NpadIdType.Player2 => 0b0011, + NpadIdType.Player3 => 0b0111, + NpadIdType.Player4 => 0b1111, + NpadIdType.Player5 => 0b1001, + NpadIdType.Player6 => 0b0101, + NpadIdType.Player7 => 0b1101, + NpadIdType.Player8 => 0b0110, + NpadIdType.Unknown => 0b0000, + NpadIdType.Handheld => 0b0000, + _ => throw new ArgumentOutOfRangeException(nameof(npadId)) + }; + + context.ResponseData.Write(ledPattern); + + return ResultCode.Success; + } + + [CommandCmif(109)] // 5.0.0+ + // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, ulong revision) + public ResultCode ActivateNpadWithRevision(ServiceCtx context) + { + ulong revision = context.RequestData.ReadUInt64(); + + return ActiveNpadImpl(context, revision); + } + + private ResultCode ActiveNpadImpl(ServiceCtx context, ulong revision = 0) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Npads.Active = true; + + // Initialize entries to avoid issues with some games. + + List<GamepadInput> emptyGamepadInputs = new List<GamepadInput>(); + List<SixAxisInput> emptySixAxisInputs = new List<SixAxisInput>(); + + for (int player = 0; player < NpadDevices.MaxControllers; player++) + { + GamepadInput gamepadInput = new GamepadInput(); + SixAxisInput sixaxisInput = new SixAxisInput(); + + gamepadInput.PlayerId = (PlayerIndex)player; + sixaxisInput.PlayerId = (PlayerIndex)player; + + sixaxisInput.Orientation = new float[9]; + + emptyGamepadInputs.Add(gamepadInput); + emptySixAxisInputs.Add(sixaxisInput); + } + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Npads.Update(emptyGamepadInputs); + context.Device.Hid.Npads.UpdateSixAxis(emptySixAxisInputs); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, revision }); + + return ResultCode.Success; + } + + [CommandCmif(120)] + // SetNpadJoyHoldType(nn::applet::AppletResourceUserId, ulong NpadJoyHoldType) + public ResultCode SetNpadJoyHoldType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + NpadJoyHoldType npadJoyHoldType = (NpadJoyHoldType)context.RequestData.ReadUInt64(); + + if (npadJoyHoldType > NpadJoyHoldType.Horizontal) + { + throw new ArgumentOutOfRangeException(nameof(npadJoyHoldType)); + } + + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.Device.Hid.Npads.JoyHold = npadJoyHoldType; + + return ResultCode.Success; + } + + [CommandCmif(121)] + // GetNpadJoyHoldType(nn::applet::AppletResourceUserId) -> ulong NpadJoyHoldType + public ResultCode GetNpadJoyHoldType(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.ResponseData.Write((ulong)context.Device.Hid.Npads.JoyHold); + + return ResultCode.Success; + } + + [CommandCmif(122)] + // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + } + + return ResultCode.Success; + } + + [CommandCmif(123)] + // SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType) + public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _); + } + + return ResultCode.Success; + } + + [CommandCmif(124)] + // SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual; + } + + return ResultCode.Success; + } + + [CommandCmif(125)] + // MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId) + public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context) + { + NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32(); + NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1)) + { + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 }); + } + + return ResultCode.Success; + } + + [CommandCmif(126)] + // StartLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StartLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(127)] + // StopLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StopLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(128)] + // SetNpadHandheldActivationMode(nn::applet::AppletResourceUserId, long HidNpadHandheldActivationMode) + public ResultCode SetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + _npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [CommandCmif(129)] + // GetNpadHandheldActivationMode(nn::applet::AppletResourceUserId) -> long HidNpadHandheldActivationMode + public ResultCode GetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((long)_npadHandheldActivationMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [CommandCmif(130)] + // SwapNpadAssignment(uint OldNpadAssignment, uint NewNpadAssignment, nn::applet::AppletResourceUserId) + public ResultCode SwapNpadAssignment(ServiceCtx context) + { + int oldNpadAssignment = context.RequestData.ReadInt32(); + int newNpadAssignment = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, oldNpadAssignment, newNpadAssignment }); + + return ResultCode.Success; + } + + [CommandCmif(131)] + // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(132)] + // EnableUnintendedHomeButtonInputProtection(bool Enable, uint Unknown0, nn::applet::AppletResourceUserId) + public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context) + { + _unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean(); + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(133)] // 5.0.0+ + // SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet + public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet); + + if (npadIdTypeIsSet) + { + context.ResponseData.Write(npadIdTypeIsSet); + context.ResponseData.Write((uint)npadIdTypeSet); + } + } + + return ResultCode.Success; + } + + private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet) + { + npadIdTypeSet = default; + npadIdTypeIsSet = false; + + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + + // TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual. + // If one is found, it returns the npadIdType of the other Npad and a bool. + // If not, it returns nothing. + } + + [CommandCmif(200)] + // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo + public ResultCode GetVibrationDeviceInfo(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>(); + NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; + NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; + + if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) + { + if (!HidUtils.IsValidNpadIdType(npadIdType)) + { + return ResultCode.InvalidNpadIdType; + } + + if (deviceHandle.Position > 1) + { + return ResultCode.InvalidDeviceIndex; + } + + VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None; + + if (Enum.IsDefined(deviceType)) + { + vibrationDeviceType = VibrationDeviceType.LinearResonantActuator; + } + else if ((uint)deviceType == 8) + { + vibrationDeviceType = VibrationDeviceType.GcErm; + } + + VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None; + + if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator) + { + if (deviceHandle.Position == 0) + { + vibrationDevicePosition = VibrationDevicePosition.Left; + } + else if (deviceHandle.Position == 1) + { + vibrationDevicePosition = VibrationDevicePosition.Right; + } + else + { + throw new ArgumentOutOfRangeException(nameof(deviceHandle.Position)); + } + } + + VibrationDeviceValue deviceInfo = new VibrationDeviceValue + { + DeviceType = vibrationDeviceType, + Position = vibrationDevicePosition + }; + + context.ResponseData.WriteStruct(deviceInfo); + + return ResultCode.Success; + } + + return ResultCode.InvalidNpadDeviceType; + } + + [CommandCmif(201)] + // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationValue(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() + }; + + VibrationValue vibrationValue = new VibrationValue + { + AmplitudeLow = context.RequestData.ReadSingle(), + FrequencyLow = context.RequestData.ReadSingle(), + AmplitudeHigh = context.RequestData.ReadSingle(), + FrequencyHigh = context.RequestData.ReadSingle() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>(); + + dualVibrationValues[deviceHandle.Position] = vibrationValue; + + context.Device.Hid.Npads.UpdateRumbleQueue((PlayerIndex)deviceHandle.PlayerId, dualVibrationValues); + + return ResultCode.Success; + } + + [CommandCmif(202)] + // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue + public ResultCode GetActualVibrationValue(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte() + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); + + context.ResponseData.Write(vibrationValue.AmplitudeLow); + context.ResponseData.Write(vibrationValue.FrequencyLow); + context.ResponseData.Write(vibrationValue.AmplitudeHigh); + context.ResponseData.Write(vibrationValue.FrequencyHigh); + + return ResultCode.Success; + } + + [CommandCmif(203)] + // CreateActiveVibrationDeviceList() -> object<nn::hid::IActiveVibrationDeviceList> + public ResultCode CreateActiveVibrationDeviceList(ServiceCtx context) + { + MakeObject(context, new IActiveApplicationDeviceList()); + + return ResultCode.Success; + } + + [CommandCmif(204)] + // PermitVibration(bool Enable) + public ResultCode PermitVibration(ServiceCtx context) + { + _vibrationPermitted = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _vibrationPermitted }); + + return ResultCode.Success; + } + + [CommandCmif(205)] + // IsVibrationPermitted() -> bool IsEnabled + public ResultCode IsVibrationPermitted(ServiceCtx context) + { + context.ResponseData.Write(_vibrationPermitted); + + return ResultCode.Success; + } + + [CommandCmif(206)] + // SendVibrationValues(nn::applet::AppletResourceUserId, buffer<array<nn::hid::VibrationDeviceHandle>, type: 9>, buffer<array<nn::hid::VibrationValue>, type: 9>) + public ResultCode SendVibrationValues(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + byte[] vibrationDeviceHandleBuffer = new byte[context.Request.PtrBuff[0].Size]; + + context.Memory.Read(context.Request.PtrBuff[0].Position, vibrationDeviceHandleBuffer); + + byte[] vibrationValueBuffer = new byte[context.Request.PtrBuff[1].Size]; + + context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); + + Span<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer); + Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer); + + if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) + { + Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>(); + PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId; + + for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++) + { + PlayerIndex index = (PlayerIndex)deviceHandles[deviceCounter].PlayerId; + byte position = deviceHandles[deviceCounter].Position; + + if (index != currentIndex || dualVibrationValues.Count == 2) + { + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + dualVibrationValues = new Dictionary<byte, VibrationValue>(); + } + + dualVibrationValues[position] = vibrationValues[deviceCounter]; + currentIndex = index; + } + + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + } + + return ResultCode.Success; + } + + [CommandCmif(207)] // 4.0.0+ + // SendVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::hid::VibrationGcErmCommand, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long vibrationGcErmCommand = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [CommandCmif(208)] // 4.0.0+ + // GetActualVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationGcErmCommand + public ResultCode GetActualVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_vibrationGcErmCommand); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, _vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [CommandCmif(209)] // 4.0.0+ + // BeginPermitVibrationSession(nn::applet::AppletResourceUserId) + public ResultCode BeginPermitVibrationSession(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(210)] // 4.0.0+ + // EndPermitVibrationSession() + public ResultCode EndPermitVibrationSession(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(211)] // 7.0.0+ + // IsVibrationDeviceMounted(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) + public ResultCode IsVibrationDeviceMounted(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + // NOTE: Service use vibrationDeviceHandle to get the PlayerIndex. + // And return false if (npadIdType >= (NpadIdType)8 && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown) + + context.ResponseData.Write(true); + + return ResultCode.Success; + } + + [CommandCmif(300)] + // ActivateConsoleSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateConsoleSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(301)] + // StartConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(302)] + // StopConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(303)] // 5.0.0+ + // ActivateSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(304)] // 5.0.0+ + // StartSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StartSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(305)] // 5.0.0+ + // StopSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StopSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(306)] // 5.0.0+ + // InitializeSevenSixAxisSensor(array<nn::sf::NativeHandle>, ulong Counter0, array<nn::sf::NativeHandle>, ulong Counter1, nn::applet::AppletResourceUserId) + public ResultCode InitializeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long counter0 = context.RequestData.ReadInt64(); + long counter1 = context.RequestData.ReadInt64(); + + // TODO: Determine if array<nn::sf::NativeHandle> is a buffer or not... + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, counter0, counter1 }); + + return ResultCode.Success; + } + + [CommandCmif(307)] // 5.0.0+ + // FinalizeSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode FinalizeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(308)] // 5.0.0+ + // SetSevenSixAxisSensorFusionStrength(float Strength, nn::applet::AppletResourceUserId) + public ResultCode SetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + _sevenSixAxisSensorFusionStrength = context.RequestData.ReadSingle(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [CommandCmif(309)] // 5.0.0+ + // GetSevenSixAxisSensorFusionStrength(nn::applet::AppletResourceUserId) -> float Strength + public ResultCode GetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sevenSixAxisSensorFusionStrength); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [CommandCmif(310)] // 6.0.0+ + // ResetSevenSixAxisSensorTimestamp(pid, nn::applet::AppletResourceUserId) + public ResultCode ResetSevenSixAxisSensorTimestamp(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(400)] + // IsUsbFullKeyControllerEnabled() -> bool IsEnabled + public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context) + { + context.ResponseData.Write(_usbFullKeyControllerEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(401)] + // EnableUsbFullKeyController(bool Enable) + public ResultCode EnableUsbFullKeyController(ServiceCtx context) + { + _usbFullKeyControllerEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(402)] + // IsUsbFullKeyControllerConnected(uint Unknown0) -> bool Connected + public ResultCode IsUsbFullKeyControllerConnected(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //FullKeyController is always connected ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { unknown0, Connected = true }); + + return ResultCode.Success; + } + + [CommandCmif(403)] // 4.0.0+ + // HasBattery(uint NpadId) -> bool HasBattery + public ResultCode HasBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a battery ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasBattery = true }); + + return ResultCode.Success; + } + + [CommandCmif(404)] // 4.0.0+ + // HasLeftRightBattery(uint NpadId) -> bool HasLeftBattery, bool HasRightBattery + public ResultCode HasLeftRightBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a left battery ? + context.ResponseData.Write(true); //Npad always got a right battery ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasLeftBattery = true, HasRightBattery = true }); + + return ResultCode.Success; + } + + [CommandCmif(405)] // 4.0.0+ + // GetNpadInterfaceType(uint NpadId) -> uchar InterfaceType + public ResultCode GetNpadInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, NpadInterfaceType = 0 }); + + return ResultCode.Success; + } + + [CommandCmif(406)] // 4.0.0+ + // GetNpadLeftRightInterfaceType(uint NpadId) -> uchar LeftInterfaceType, uchar RightInterfaceType + public ResultCode GetNpadLeftRightInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + context.ResponseData.Write((byte)0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, LeftInterfaceType = 0, RightInterfaceType = 0 }); + + return ResultCode.Success; + } + + [CommandCmif(500)] // 5.0.0+ + // GetPalmaConnectionHandle(uint Unknown0, nn::applet::AppletResourceUserId) -> nn::hid::PalmaConnectionHandle + public ResultCode GetPalmaConnectionHandle(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + int palmaConnectionHandle = 0; + + context.ResponseData.Write(palmaConnectionHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId , unknown0, palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(501)] // 5.0.0+ + // InitializePalma(nn::hid::PalmaConnectionHandle) + public ResultCode InitializePalma(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(502)] // 5.0.0+ + // AcquirePalmaOperationCompleteEvent(nn::hid::PalmaConnectionHandle) -> nn::sf::NativeHandle + public ResultCode AcquirePalmaOperationCompleteEvent(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + if (context.Process.HandleTable.GenerateHandle(_palmaOperationCompleteEvent.ReadableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(503)] // 5.0.0+ + // GetPalmaOperationInfo(nn::hid::PalmaConnectionHandle) -> long Unknown0, buffer<Unknown> + public ResultCode GetPalmaOperationInfo(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + long unknown0 = 0; //Counter? + + context.ResponseData.Write(unknown0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(504)] // 5.0.0+ + // PlayPalmaActivity(nn::hid::PalmaConnectionHandle, ulong Unknown0) + public ResultCode PlayPalmaActivity(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(505)] // 5.0.0+ + // SetPalmaFrModeType(nn::hid::PalmaConnectionHandle, ulong FrModeType) + public ResultCode SetPalmaFrModeType(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long frModeType = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, frModeType }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(506)] // 5.0.0+ + // ReadPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(507)] // 5.0.0+ + // EnablePalmaStep(nn::hid::PalmaConnectionHandle, bool Enable) + public ResultCode EnablePalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + bool enabledPalmaStep = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, enabledPalmaStep }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(508)] // 5.0.0+ + // ResetPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ResetPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(509)] // 5.0.0+ + // ReadPalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1) + public ResultCode ReadPalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + return ResultCode.Success; + } + + [CommandCmif(510)] // 5.0.0+ + // WritePalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1, nn::hid::PalmaApplicationSectionAccessBuffer) + public ResultCode WritePalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + // nn::hid::PalmaApplicationSectionAccessBuffer cast is unknown + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(511)] // 5.0.0+ + // ReadPalmaUniqueCode(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaUniqueCode(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(512)] // 5.0.0+ + // SetPalmaUniqueCodeInvalid(nn::hid::PalmaConnectionHandle) + public ResultCode SetPalmaUniqueCodeInvalid(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(522)] // 5.1.0+ + // SetIsPalmaAllConnectable(nn::applet::AppletResourceUserId, bool, pid) + public ResultCode SetIsPalmaAllConnectable(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long unknownBool = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknownBool }); + + return ResultCode.Success; + } + + [CommandCmif(525)] // 5.1.0+ + // SetPalmaBoostMode(bool) + public ResultCode SetPalmaBoostMode(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId) + public ResultCode SetNpadCommunicationMode(ServiceCtx context) + { + _npadCommunicationMode = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadCommunicationMode }); + + return ResultCode.Success; + } + + [CommandCmif(1001)] + // GetNpadCommunicationMode() -> long CommunicationMode + public ResultCode GetNpadCommunicationMode(ServiceCtx context) + { + context.ResponseData.Write(_npadCommunicationMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _npadCommunicationMode }); + + return ResultCode.Success; + } + + [CommandCmif(1002)] // 9.0.0+ + // SetTouchScreenConfiguration(nn::hid::TouchScreenConfigurationForNx, nn::applet::AppletResourceUserId) + public ResultCode SetTouchScreenConfiguration(ServiceCtx context) + { + long touchScreenConfigurationForNx = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, touchScreenConfigurationForNx }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs new file mode 100644 index 00000000..4a5d0e9b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs @@ -0,0 +1,76 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Types; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:sys")] + class IHidSystemServer : IpcService + { + public IHidSystemServer(ServiceCtx context) { } + + [CommandCmif(303)] + // ApplyNpadSystemCommonPolicy(u64) + public ResultCode ApplyNpadSystemCommonPolicy(ServiceCtx context) + { + ulong commonPolicy = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { commonPolicy }); + + return ResultCode.Success; + } + + [CommandCmif(306)] + // GetLastActiveNpad(u32) -> u8, u8 + public ResultCode GetLastActiveNpad(ServiceCtx context) + { + // TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue. + context.RequestData.ReadUInt32(); + + ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType); + + context.ResponseData.Write((byte)appletFooterUiType); + context.ResponseData.Write((byte)0); + + return resultCode; + } + + [CommandCmif(307)] + // GetNpadSystemExtStyle() -> u64 + public ResultCode GetNpadSystemExtStyle(ServiceCtx context) + { + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.ResponseData.Write((ulong)context.Device.Hid.Npads.SupportedStyleSets); + + return ResultCode.Success; + } + + [CommandCmif(314)] // 9.0.0+ + // GetAppletFooterUiType(u32) -> u8 + public ResultCode GetAppletFooterUiType(ServiceCtx context) + { + ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType); + + context.ResponseData.Write((byte)appletFooterUiType); + + return resultCode; + } + + private ResultCode GetAppletFooterUiTypeImpl(ServiceCtx context, out AppletFooterUiType appletFooterUiType) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + PlayerIndex playerIndex = HidUtils.GetIndexFromNpadIdType(npadIdType); + + appletFooterUiType = context.Device.Hid.SharedMemory.Npads[(int)playerIndex].InternalState.AppletFooterUiType; + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs new file mode 100644 index 00000000..bfd1d4dc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hidbus")] + class IHidbusServer : IpcService + { + public IHidbusServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs new file mode 100644 index 00000000..71353344 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("xcd:sys")] + class ISystemServer : IpcService + { + public ISystemServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs new file mode 100644 index 00000000..130fcf68 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs @@ -0,0 +1,240 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Irs.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs")] + class IIrSensorServer : IpcService + { + private int _irsensorSharedMemoryHandle = 0; + + public IIrSensorServer(ServiceCtx context) { } + + [CommandCmif(302)] + // ActivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode ActivateIrsensor(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // NOTE: This seems to initialize the shared memory for irs service. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(303)] + // DeactivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode DeactivateIrsensor(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // NOTE: This seems to deinitialize the shared memory for irs service. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(304)] + // GetIrsensorSharedMemoryHandle(nn::applet::AppletResourceUserId, pid) -> handle<copy> + public ResultCode GetIrsensorSharedMemoryHandle(ServiceCtx context) + { + // NOTE: Shared memory should use the appletResourceUserId. + // ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + if (_irsensorSharedMemoryHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _irsensorSharedMemoryHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_irsensorSharedMemoryHandle); + + return ResultCode.Success; + } + + [CommandCmif(305)] + // StopImageProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId) + public ResultCode StopImageProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType }); + + return ResultCode.Success; + } + + [CommandCmif(306)] + // RunMomentProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedMomentProcessorConfig) + public ResultCode RunMomentProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedMomentProcessorConfig = context.RequestData.ReadStruct<PackedMomentProcessorConfig>(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedMomentProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(307)] + // RunClusteringProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedClusteringProcessorConfig) + public ResultCode RunClusteringProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedClusteringProcessorConfig = context.RequestData.ReadStruct<PackedClusteringProcessorConfig>(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedClusteringProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(308)] + // RunImageTransferProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedImageTransferProcessorConfig, u64 TransferMemorySize, TransferMemoryHandle) + public ResultCode RunImageTransferProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedImageTransferProcessorConfig = context.RequestData.ReadStruct<PackedImageTransferProcessorConfig>(); + + CheckCameraHandle(irCameraHandle); + + // TODO: Handle the Transfer Memory. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedImageTransferProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(309)] + // GetImageTransferProcessorState(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId) + public ResultCode GetImageTransferProcessorState(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // ulong imageTransferBufferAddress = context.Request.ReceiveBuff[0].Position; + ulong imageTransferBufferSize = context.Request.ReceiveBuff[0].Size; + + if (imageTransferBufferSize == 0) + { + return ResultCode.InvalidBufferSize; + } + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType }); + + // TODO: Uses the buffer to copy the JoyCon IR data (by using a JoyCon driver) and update the following struct. + context.ResponseData.WriteStruct(new ImageTransferProcessorState() + { + SamplingNumber = 0, + AmbientNoiseLevel = 0 + }); + + return ResultCode.Success; + } + + [CommandCmif(310)] + // RunTeraPluginProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedTeraPluginProcessorConfig) + public ResultCode RunTeraPluginProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedTeraPluginProcessorConfig = context.RequestData.ReadStruct<PackedTeraPluginProcessorConfig>(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedTeraPluginProcessorConfig.RequiredMcuVersion }); + + return ResultCode.Success; + } + + [CommandCmif(311)] + // GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle + public ResultCode GetNpadIrCameraHandle(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + + if (npadIdType > NpadIdType.Player8 && + npadIdType != NpadIdType.Unknown && + npadIdType != NpadIdType.Handheld) + { + return ResultCode.NpadIdOutOfRange; + } + + PlayerIndex irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType); + + context.ResponseData.Write((int)irCameraHandle); + + // NOTE: If the irCameraHandle pointer is null this error is returned, Doesn't occur in our case. + // return ResultCode.HandlePointerIsNull; + + return ResultCode.Success; + } + + [CommandCmif(314)] // 3.0.0+ + // CheckFirmwareVersion(nn::irsensor::IrCameraHandle, nn::irsensor::PackedMcuVersion, nn::applet::AppletResourceUserId, pid) + public ResultCode CheckFirmwareVersion(ServiceCtx context) + { + int irCameraHandle = context.RequestData.ReadInt32(); + short packedMcuVersionMajor = context.RequestData.ReadInt16(); + short packedMcuVersionMinor = context.RequestData.ReadInt16(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle, packedMcuVersionMajor, packedMcuVersionMinor }); + + return ResultCode.Success; + } + + [CommandCmif(318)] // 4.0.0+ + // StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid) + public ResultCode StopImageProcessorAsync(ServiceCtx context) + { + int irCameraHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle }); + + return ResultCode.Success; + } + + [CommandCmif(319)] // 4.0.0+ + // ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid) + public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long packedFunctionLevel = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, packedFunctionLevel }); + + return ResultCode.Success; + } + + private ResultCode CheckCameraHandle(IrCameraHandle irCameraHandle) + { + if (irCameraHandle.DeviceType == 1 || (PlayerIndex)irCameraHandle.PlayerNumber >= PlayerIndex.Unknown) + { + return ResultCode.InvalidCameraHandle; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs new file mode 100644 index 00000000..99fcd541 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs:sys")] + class IIrSensorSystemServer : IpcService + { + public IIrSensorSystemServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs new file mode 100644 index 00000000..3afc03c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + public enum ResultCode + { + ModuleId = 205, + ErrorCodeShift = 9, + + Success = 0, + + InvalidCameraHandle = (204 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (207 << ErrorCodeShift) | ModuleId, + HandlePointerIsNull = (212 << ErrorCodeShift) | ModuleId, + NpadIdOutOfRange = (709 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs new file mode 100644 index 00000000..647aef64 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct ImageTransferProcessorState + { + public ulong SamplingNumber; + public uint AmbientNoiseLevel; + public uint Reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs new file mode 100644 index 00000000..8ed7201e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4)] + struct IrCameraHandle + { + public byte PlayerNumber; + public byte DeviceType; + public ushort Reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs new file mode 100644 index 00000000..735f7822 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + struct PackedClusteringProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public ushort WindowOfInterestX; + public ushort WindowOfInterestY; + public ushort WindowOfInterestWidth; + public ushort WindowOfInterestHeight; + public uint RequiredMcuVersion; + public uint ObjectPixelCountMin; + public uint ObjectPixelCountMax; + public byte ObjectIntensityMin; + public byte IsExternalLightFilterEnabled; + public ushort Reserved3; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs new file mode 100644 index 00000000..094413e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct PackedImageTransferProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public uint RequiredMcuVersion; + public byte Format; + public byte Reserved3; + public ushort Reserved4; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs new file mode 100644 index 00000000..a1b70b40 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct PackedMomentProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public ushort WindowOfInterestX; + public ushort WindowOfInterestY; + public ushort WindowOfInterestWidth; + public ushort WindowOfInterestHeight; + public uint RequiredMcuVersion; + public byte Preprocess; + public byte PreprocessIntensityThreshold; + public ushort Reserved3; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs new file mode 100644 index 00000000..808b0b72 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct PackedTeraPluginProcessorConfig + { + public uint RequiredMcuVersion; + public byte Mode; + public byte Unknown1; + public byte Unknown2; + public byte Unknown3; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs new file mode 100644 index 00000000..9c87ac1d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + enum ResultCode + { + ModuleId = 202, + ErrorCodeShift = 9, + + Success = 0, + + InvalidNpadDeviceType = (122 << ErrorCodeShift) | ModuleId, + InvalidNpadIdType = (123 << ErrorCodeShift) | ModuleId, + InvalidDeviceIndex = (124 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (131 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs new file mode 100644 index 00000000..c4ff8d7e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs @@ -0,0 +1,30 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + [Flags] + enum AppletFooterUiType : byte + { + None, + HandheldNone, + HandheldJoyConLeftOnly, + HandheldJoyConRightOnly, + HandheldJoyConLeftJoyConRight, + JoyDual, + JoyDualLeftOnly, + JoyDualRightOnly, + JoyLeftHorizontal, + JoyLeftVertical, + JoyRightHorizontal, + JoyRightVertical, + SwitchProController, + CompatibleProController, + CompatibleJoyCon, + LarkHvc1, + LarkHvc2, + LarkNesLeft, + LarkNesRight, + Lucia, + Verification + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs new file mode 100644 index 00000000..18d9fd9c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + struct HidVector + { + public float X; + public float Y; + public float Z; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs new file mode 100644 index 00000000..c91636b2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Flags] + public enum ControllerKeys : long + { + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + LStick = 1 << 4, + RStick = 1 << 5, + L = 1 << 6, + R = 1 << 7, + Zl = 1 << 8, + Zr = 1 << 9, + Plus = 1 << 10, + Minus = 1 << 11, + DpadLeft = 1 << 12, + DpadUp = 1 << 13, + DpadRight = 1 << 14, + DpadDown = 1 << 15, + LStickLeft = 1 << 16, + LStickUp = 1 << 17, + LStickRight = 1 << 18, + LStickDown = 1 << 19, + RStickLeft = 1 << 20, + RStickUp = 1 << 21, + RStickRight = 1 << 22, + RStickDown = 1 << 23, + SlLeft = 1 << 24, + SrLeft = 1 << 25, + SlRight = 1 << 26, + SrRight = 1 << 27, + + // Generic Catch-all + Up = DpadUp | LStickUp | RStickUp, + Down = DpadDown | LStickDown | RStickDown, + Left = DpadLeft | LStickLeft | RStickLeft, + Right = DpadRight | LStickRight | RStickRight, + Sl = SlLeft | SlRight, + Sr = SrLeft | SrRight + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs new file mode 100644 index 00000000..b2d34e8e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Flags] + public enum ControllerType : int + { + None, + ProController = 1 << 0, + Handheld = 1 << 1, + JoyconPair = 1 << 2, + JoyconLeft = 1 << 3, + JoyconRight = 1 << 4, + Invalid = 1 << 5, + Pokeball = 1 << 6, + SystemExternal = 1 << 29, + System = 1 << 30 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs new file mode 100644 index 00000000..3c311e21 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadColor : uint + { + BodyGray = 0x828282, + BodyNeonRed = 0xFF3C28, + BodyNeonBlue = 0x0AB9E6, + BodyNeonYellow = 0xE6FF00, + BodyNeonGreen = 0x1EDC00, + BodyNeonPink = 0xFF3278, + BodyRed = 0xE10F00, + BodyBlue = 0x4655F5, + BodyNeonPurple = 0xB400E6, + BodyNeonOrange = 0xFAA005, + BodyPokemonLetsGoPikachu = 0xFFDC00, + BodyPokemonLetsGoEevee = 0xC88C32, + BodyNintendoLaboCreatorsContestEdition = 0xD7AA73, + BodyAnimalCrossingSpecialEditionLeftJoyCon = 0x82FF96, + BodyAnimalCrossingSpecialEditionRightJoyCon = 0x96F5F5, + + ButtonGray = 0x0F0F0F, + ButtonNeonRed = 0x1E0A0A, + ButtonNeonBlue = 0x001E1E, + ButtonNeonYellow = 0x142800, + ButtonNeonGreen = 0x002800, + ButtonNeonPink = 0x28001E, + ButtonRed = 0x280A0A, + ButtonBlue = 0x00000A, + ButtonNeonPurple = 0x140014, + ButtonNeonOrange = 0x0F0A00, + ButtonPokemonLetsGoPikachu = 0x322800, + ButtonPokemonLetsGoEevee = 0x281900, + ButtonNintendoLaboCreatorsContestEdition = 0x1E1914, + ButtonAnimalCrossingSpecialEditionLeftJoyCon = 0x0A1E0A, + ButtonAnimalCrossingSpecialEditionRightJoyCon = 0x0A1E28 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs new file mode 100644 index 00000000..1abd8468 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadIdType : int + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Unknown = 16, + Handheld = 32 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs new file mode 100644 index 00000000..ddf5d97f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadStyleIndex : byte + { + FullKey = 3, + Handheld = 4, + JoyDual = 5, + JoyLeft = 6, + JoyRight = 7, + SystemExt = 32, + System = 33 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs new file mode 100644 index 00000000..f4ced5df --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum PlayerIndex : int + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Handheld = 8, + Unknown = 9, + Auto = 10 // Shouldn't be used directly + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs new file mode 100644 index 00000000..d3b51a24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + enum NpadJoyHoldType + { + Vertical, + Horizontal + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs new file mode 100644 index 00000000..bf4b5888 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct AnalogStickState + { + public int X; + public int Y; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs new file mode 100644 index 00000000..da53e421 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs @@ -0,0 +1,26 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct AtomicStorage<T> where T: unmanaged, ISampledDataStruct + { + public ulong SamplingNumber; + public T Object; + + public ulong ReadSamplingNumberAtomic() + { + return Interlocked.Read(ref SamplingNumber); + } + + public void SetObject(ref T obj) + { + ulong samplingNumber = ISampledDataStruct.GetSamplingNumber(ref obj); + + Interlocked.Exchange(ref SamplingNumber, samplingNumber); + + Thread.MemoryBarrier(); + + Object = obj; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs new file mode 100644 index 00000000..a382c0c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs @@ -0,0 +1,65 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + /// <summary> + /// This is a "marker interface" to add some compile-time safety to a convention-based optimization. + /// + /// Any struct implementing this interface should: + /// - use <c>StructLayoutAttribute</c> (and related attributes) to explicity control how the struct is laid out in memory. + /// - ensure that the method <c>ISampledDataStruct.GetSamplingNumberFieldOffset()</c> correctly returns the offset, in bytes, + /// to the ulong "Sampling Number" field within the struct. Most types have it as the first field, so the default offset is 0. + /// + /// Example: + /// + /// <c> + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct DebugPadState : ISampledDataStruct + /// { + /// public ulong SamplingNumber; // 1st field, so no need to add special handling to GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct SixAxisSensorState : ISampledDataStruct + /// { + /// public ulong DeltaTime; + /// public ulong SamplingNumber; // Not the first field - needs special handling in GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// </c> + /// </summary> + internal interface ISampledDataStruct + { + // No Instance Members - marker interface only + + public static ulong GetSamplingNumber<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + ReadOnlySpan<T> structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1); + + ReadOnlySpan<byte> byteSpan = MemoryMarshal.Cast<T, byte>(structSpan); + + int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct); + + if (fieldOffset > 0) + { + byteSpan = byteSpan.Slice(fieldOffset); + } + + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan); + + return value; + } + + private static int GetSamplingNumberFieldOffset<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + return sampledDataStruct switch + { + Npad.SixAxisSensorState _ => sizeof(ulong), + _ => 0 + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs new file mode 100644 index 00000000..ae654d6f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs @@ -0,0 +1,149 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct RingLifo<T> where T: unmanaged, ISampledDataStruct + { + private const ulong MaxEntries = 17; + +#pragma warning disable CS0169 + private ulong _unused; +#pragma warning restore CS0169 +#pragma warning disable CS0414 + private ulong _bufferCount; +#pragma warning restore CS0414 + private ulong _index; + private ulong _count; + private Array17<AtomicStorage<T>> _storage; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong ReadCurrentIndex() + { + return Interlocked.Read(ref _index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong ReadCurrentCount() + { + return Interlocked.Read(ref _count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong GetNextIndexForWrite(ulong index) + { + return (index + 1) % MaxEntries; + } + + public ref AtomicStorage<T> GetCurrentAtomicEntryRef() + { + ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), 1); + + if (countAvailaible == 0) + { + _storage[0] = default; + + return ref _storage[0]; + } + + ulong index = ReadCurrentIndex(); + + while (true) + { + int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible) % MaxEntries); + + ref AtomicStorage<T> result = ref _storage[inputEntryIndex]; + + ulong samplingNumber0 = result.ReadSamplingNumberAtomic(); + ulong samplingNumber1 = result.ReadSamplingNumberAtomic(); + + if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1) + { + ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible); + + countAvailaible = Math.Min(tempCount, 1); + index = ReadCurrentIndex(); + + continue; + } + + return ref result; + } + } + + public ref T GetCurrentEntryRef() + { + return ref GetCurrentAtomicEntryRef().Object; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan<AtomicStorage<T>> ReadEntries(uint maxCount) + { + ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), maxCount); + + if (countAvailaible == 0) + { + return ReadOnlySpan<AtomicStorage<T>>.Empty; + } + + ulong index = ReadCurrentIndex(); + + AtomicStorage<T>[] result = new AtomicStorage<T>[countAvailaible]; + + for (ulong i = 0; i < countAvailaible; i++) + { + int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible + i) % MaxEntries); + int outputEntryIndex = (int)(countAvailaible - i - 1); + + ulong samplingNumber0 = _storage[inputEntryIndex].ReadSamplingNumberAtomic(); + result[outputEntryIndex] = _storage[inputEntryIndex]; + ulong samplingNumber1 = _storage[inputEntryIndex].ReadSamplingNumberAtomic(); + + if (samplingNumber0 != samplingNumber1 && (i > 0 && (result[outputEntryIndex].SamplingNumber - result[outputEntryIndex].SamplingNumber) != 1)) + { + ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible); + + countAvailaible = Math.Min(tempCount, maxCount); + index = ReadCurrentIndex(); + + i -= 1; + } + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ref T value) + { + ulong targetIndex = GetNextIndexForWrite(ReadCurrentIndex()); + + _storage[(int)targetIndex].SetObject(ref value); + + Interlocked.Exchange(ref _index, targetIndex); + + ulong count = ReadCurrentCount(); + + if (count < (MaxEntries - 1)) + { + Interlocked.Increment(ref _count); + } + } + + public void Clear() + { + Interlocked.Exchange(ref _count, 0); + Interlocked.Exchange(ref _index, 0); + } + + public static RingLifo<T> Create() + { + return new RingLifo<T> + { + _bufferCount = MaxEntries + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs new file mode 100644 index 00000000..ec5bd3c8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [Flags] + enum DebugPadAttribute : uint + { + None = 0, + Connected = 1 << 0 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs new file mode 100644 index 00000000..e8f28317 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [Flags] + enum DebugPadButton : uint + { + None = 0, + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + L = 1 << 4, + R = 1 << 5, + ZL = 1 << 6, + ZR = 1 << 7, + Start = 1 << 8, + Select = 1 << 9, + Left = 1 << 10, + Up = 1 << 11, + Right = 1 << 12, + Down = 1 << 13 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs new file mode 100644 index 00000000..0846cfc7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DebugPadState : ISampledDataStruct + { + public ulong SamplingNumber; + public DebugPadAttribute Attributes; + public DebugPadButton Buttons; + public AnalogStickState AnalogStickR; + public AnalogStickState AnalogStickL; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs new file mode 100644 index 00000000..22df7c79 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + struct KeyboardKey + { + public Array4<ulong> RawData; + + public bool this[KeyboardKeyShift index] + { + get + { + return (RawData[(int)index / 64] & (1UL << ((int)index & 63))) != 0; + } + set + { + int arrayIndex = (int)index / 64; + ulong mask = 1UL << ((int)index & 63); + + RawData[arrayIndex] &= ~mask; + + if (value) + { + RawData[arrayIndex] |= mask; + } + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs new file mode 100644 index 00000000..01c2bb30 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs @@ -0,0 +1,138 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + enum KeyboardKeyShift + { + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + D1 = 30, + D2 = 31, + D3 = 32, + D4 = 33, + D5 = 34, + D6 = 35, + D7 = 36, + D8 = 37, + D9 = 38, + D0 = 39, + Return = 40, + Escape = 41, + Backspace = 42, + Tab = 43, + Space = 44, + Minus = 45, + Plus = 46, + OpenBracket = 47, + CloseBracket = 48, + Pipe = 49, + Tilde = 50, + Semicolon = 51, + Quote = 52, + Backquote = 53, + Comma = 54, + Period = 55, + Slash = 56, + CapsLock = 57, + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + PrintScreen = 70, + ScrollLock = 71, + Pause = 72, + Insert = 73, + Home = 74, + PageUp = 75, + Delete = 76, + End = 77, + PageDown = 78, + RightArrow = 79, + LeftArrow = 80, + DownArrow = 81, + UpArrow = 82, + NumLock = 83, + NumPadDivide = 84, + NumPadMultiply = 85, + NumPadSubtract = 86, + NumPadAdd = 87, + NumPadEnter = 88, + NumPad1 = 89, + NumPad2 = 90, + NumPad3 = 91, + NumPad4 = 92, + NumPad5 = 93, + NumPad6 = 94, + NumPad7 = 95, + NumPad8 = 96, + NumPad9 = 97, + NumPad0 = 98, + NumPadDot = 99, + Backslash = 100, + Application = 101, + Power = 102, + NumPadEquals = 103, + F13 = 104, + F14 = 105, + F15 = 106, + F16 = 107, + F17 = 108, + F18 = 109, + F19 = 110, + F20 = 111, + F21 = 112, + F22 = 113, + F23 = 114, + F24 = 115, + NumPadComma = 133, + Ro = 135, + KatakanaHiragana = 136, + Yen = 137, + Henkan = 138, + Muhenkan = 139, + NumPadCommaPc98 = 140, + HangulEnglish = 144, + Hanja = 145, + Katakana = 146, + Hiragana = 147, + ZenkakuHankaku = 148, + LeftControl = 224, + LeftShift = 225, + LeftAlt = 226, + LeftGui = 227, + RightControl = 228, + RightShift = 229, + RightAlt = 230, + RightGui = 231 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs new file mode 100644 index 00000000..839a4e82 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + [Flags] + enum KeyboardModifier : ulong + { + None = 0, + Control = 1 << 0, + Shift = 1 << 1, + LeftAlt = 1 << 2, + RightAlt = 1 << 3, + Gui = 1 << 4, + CapsLock = 1 << 8, + ScrollLock = 1 << 9, + NumLock = 1 << 10, + Katakana = 1 << 11, + Hiragana = 1 << 12 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs new file mode 100644 index 00000000..4de92813 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct KeyboardState : ISampledDataStruct + { + public ulong SamplingNumber; + public KeyboardModifier Modifiers; + public KeyboardKey Keys; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs new file mode 100644 index 00000000..5ffba0d7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [Flags] + enum MouseAttribute : uint + { + None = 0, + Transferable = 1 << 0, + IsConnected = 1 << 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs new file mode 100644 index 00000000..7e35140c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [Flags] + enum MouseButton : uint + { + None = 0, + Left = 1 << 0, + Right = 1 << 1, + Middle = 1 << 2, + Forward = 1 << 3, + Back = 1 << 4 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs new file mode 100644 index 00000000..c953c794 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MouseState : ISampledDataStruct + { + public ulong SamplingNumber; + public int X; + public int Y; + public int DeltaX; + public int DeltaY; + public int WheelDeltaX; + public int WheelDeltaY; + public MouseButton Buttons; + public MouseAttribute Attributes; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs new file mode 100644 index 00000000..b0201835 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs @@ -0,0 +1,29 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum DeviceType : int + { + None = 0, + + FullKey = 1 << 0, + DebugPad = 1 << 1, + HandheldLeft = 1 << 2, + HandheldRight = 1 << 3, + JoyLeft = 1 << 4, + JoyRight = 1 << 5, + Palma = 1 << 6, + LarkHvcLeft = 1 << 7, + LarkHvcRight = 1 << 8, + LarkNesLeft = 1 << 9, + LarkNesRight = 1 << 10, + HandheldLarkHvcLeft = 1 << 11, + HandheldLarkHvcRight = 1 << 12, + HandheldLarkNesLeft = 1 << 13, + HandheldLarkNesRight = 1 << 14, + Lucia = 1 << 15, + + System = 1 << 31 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs new file mode 100644 index 00000000..0960b7bf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadAttribute : uint + { + None = 0, + IsConnected = 1 << 0, + IsWired = 1 << 1, + IsLeftConnected = 1 << 2, + IsLeftWired = 1 << 3, + IsRightConnected = 1 << 4, + IsRightWired = 1 << 5 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs new file mode 100644 index 00000000..477dfd10 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadBatteryLevel : int + { + Percent0, + Percent25, + Percent50, + Percent75, + Percent100 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs new file mode 100644 index 00000000..5b3e13a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs @@ -0,0 +1,44 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadButton : ulong + { + None = 0, + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + StickL = 1 << 4, + StickR = 1 << 5, + L = 1 << 6, + R = 1 << 7, + ZL = 1 << 8, + ZR = 1 << 9, + Plus = 1 << 10, + Minus = 1 << 11, + Left = 1 << 12, + Up = 1 << 13, + Right = 1 << 14, + Down = 1 << 15, + StickLLeft = 1 << 16, + StickLUp = 1 << 17, + StickLRight = 1 << 18, + StickLDown = 1 << 19, + StickRLeft = 1 << 20, + StickRUp = 1 << 21, + StickRRight = 1 << 22, + StickRDown = 1 << 23, + LeftSL = 1 << 24, + LeftSR = 1 << 25, + RightSL = 1 << 26, + RightSR = 1 << 27, + Palma = 1 << 28, + + // FIXME: Probably a button on Lark. + Unknown29 = 1 << 29, + + HandheldLeftB = 1 << 30 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs new file mode 100644 index 00000000..1e547cc8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadColorAttribute : uint + { + Ok, + ReadError, + NoController + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs new file mode 100644 index 00000000..64f75ce9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadCommonState : ISampledDataStruct + { + public ulong SamplingNumber; + public NpadButton Buttons; + public AnalogStickState AnalogStickL; + public AnalogStickState AnalogStickR; + public NpadAttribute Attributes; + private uint _reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs new file mode 100644 index 00000000..990eafb2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadFullKeyColorState + { + public NpadColorAttribute Attribute; + public uint FullKeyBody; + public uint FullKeyButtons; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs new file mode 100644 index 00000000..bddd6212 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadGcTriggerState : ISampledDataStruct + { +#pragma warning disable CS0649 + public ulong SamplingNumber; + public uint TriggerL; + public uint TriggerR; +#pragma warning restore CS0649 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs new file mode 100644 index 00000000..b009f95e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs @@ -0,0 +1,65 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadInternalState + { + public NpadStyleTag StyleSet; + public NpadJoyAssignmentMode JoyAssignmentMode; + public NpadFullKeyColorState FullKeyColor; + public NpadJoyColorState JoyColor; + public RingLifo<NpadCommonState> FullKey; + public RingLifo<NpadCommonState> Handheld; + public RingLifo<NpadCommonState> JoyDual; + public RingLifo<NpadCommonState> JoyLeft; + public RingLifo<NpadCommonState> JoyRight; + public RingLifo<NpadCommonState> Palma; + public RingLifo<NpadCommonState> SystemExt; + public RingLifo<SixAxisSensorState> FullKeySixAxisSensor; + public RingLifo<SixAxisSensorState> HandheldSixAxisSensor; + public RingLifo<SixAxisSensorState> JoyDualSixAxisSensor; + public RingLifo<SixAxisSensorState> JoyDualRightSixAxisSensor; + public RingLifo<SixAxisSensorState> JoyLeftSixAxisSensor; + public RingLifo<SixAxisSensorState> JoyRightSixAxisSensor; + public DeviceType DeviceType; + private uint _reserved1; + public NpadSystemProperties SystemProperties; + public NpadSystemButtonProperties SystemButtonProperties; + public NpadBatteryLevel BatteryLevelJoyDual; + public NpadBatteryLevel BatteryLevelJoyLeft; + public NpadBatteryLevel BatteryLevelJoyRight; + public uint AppletFooterUiAttributes; + public AppletFooterUiType AppletFooterUiType; + private Reserved2Struct _reserved2; + public RingLifo<NpadGcTriggerState> GcTrigger; + public NpadLarkType LarkTypeLeftAndMain; + public NpadLarkType LarkTypeRight; + public NpadLuciaType LuciaType; + public uint Unknown43EC; + + [StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)] + private struct Reserved2Struct {} + + public static NpadInternalState Create() + { + return new NpadInternalState + { + FullKey = RingLifo<NpadCommonState>.Create(), + Handheld = RingLifo<NpadCommonState>.Create(), + JoyDual = RingLifo<NpadCommonState>.Create(), + JoyLeft = RingLifo<NpadCommonState>.Create(), + JoyRight = RingLifo<NpadCommonState>.Create(), + Palma = RingLifo<NpadCommonState>.Create(), + SystemExt = RingLifo<NpadCommonState>.Create(), + FullKeySixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + HandheldSixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + JoyDualSixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + JoyDualRightSixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + JoyLeftSixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + JoyRightSixAxisSensor = RingLifo<SixAxisSensorState>.Create(), + GcTrigger = RingLifo<NpadGcTriggerState>.Create(), + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs new file mode 100644 index 00000000..871c4c5a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadJoyAssignmentMode : uint + { + Dual, + Single + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs new file mode 100644 index 00000000..3986dd5e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadJoyColorState + { + public NpadColorAttribute Attribute; + public uint LeftBody; + public uint LeftButtons; + public uint RightBody; + public uint RightButtons; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs new file mode 100644 index 00000000..a487a911 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadLarkType : uint + { + Invalid, + H1, + H2, + NL, + NR + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs new file mode 100644 index 00000000..95148485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadLuciaType + { + Invalid, + J, + E, + U + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs new file mode 100644 index 00000000..ed9e7c0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5000)] + struct NpadState + { + public NpadInternalState InternalState; + + public static NpadState Create() + { + return new NpadState + { + InternalState = NpadInternalState.Create() + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs new file mode 100644 index 00000000..f31978e2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs @@ -0,0 +1,76 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + /// <summary> + /// Nintendo pad style + /// </summary> + [Flags] + enum NpadStyleTag : uint + { + /// <summary> + /// No type. + /// </summary> + None = 0, + + /// <summary> + /// Pro controller. + /// </summary> + FullKey = 1 << 0, + + /// <summary> + /// Joy-Con controller in handheld mode. + /// </summary> + Handheld = 1 << 1, + + /// <summary> + /// Joy-Con controller in dual mode. + /// </summary> + JoyDual = 1 << 2, + + /// <summary> + /// Joy-Con left controller in single mode. + /// </summary> + JoyLeft = 1 << 3, + + /// <summary> + /// Joy-Con right controller in single mode. + /// </summary> + JoyRight = 1 << 4, + + /// <summary> + /// GameCube controller. + /// </summary> + Gc = 1 << 5, + + /// <summary> + /// Poké Ball Plus controller. + /// </summary> + Palma = 1 << 6, + + /// <summary> + /// NES and Famicom controller. + /// </summary> + Lark = 1 << 7, + + /// <summary> + /// NES and Famicom controller in handheld mode. + /// </summary> + HandheldLark = 1 << 8, + + /// <summary> + /// SNES controller. + /// </summary> + Lucia = 1 << 9, + + /// <summary> + /// Generic external controller. + /// </summary> + SystemExt = 1 << 29, + + /// <summary> + /// Generic controller. + /// </summary> + System = 1 << 30 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs new file mode 100644 index 00000000..68603271 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadSystemButtonProperties : uint + { + None = 0, + IsUnintendedHomeButtonInputProtectionEnabled = 1 << 0 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs new file mode 100644 index 00000000..13444555 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadSystemProperties : ulong + { + None = 0, + + IsChargingJoyDual = 1 << 0, + IsChargingJoyLeft = 1 << 1, + IsChargingJoyRight = 1 << 2, + IsPoweredJoyDual = 1 << 3, + IsPoweredJoyLeft = 1 << 4, + IsPoweredJoyRight = 1 << 5, + IsUnsuportedButtonPressedOnNpadSystem = 1 << 9, + IsUnsuportedButtonPressedOnNpadSystemExt = 1 << 10, + IsAbxyButtonOriented = 1 << 11, + IsSlSrButtonOriented = 1 << 12, + IsPlusAvailable = 1 << 13, + IsMinusAvailable = 1 << 14, + IsDirectionalButtonsAvailable = 1 << 15 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs new file mode 100644 index 00000000..7ed46d98 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum SixAxisSensorAttribute : uint + { + None = 0, + IsConnected = 1 << 0, + IsInterpolated = 1 << 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs new file mode 100644 index 00000000..18be3276 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SixAxisSensorState : ISampledDataStruct + { + public ulong DeltaTime; + public ulong SamplingNumber; + public HidVector Acceleration; + public HidVector AngularVelocity; + public HidVector Angle; + public Array9<float> Direction; + public SixAxisSensorAttribute Attributes; + private uint _reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs new file mode 100644 index 00000000..48acfc3f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs @@ -0,0 +1,66 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory +{ + /// <summary> + /// Represent the shared memory shared between applications for input. + /// </summary> + [StructLayout(LayoutKind.Explicit, Size = 0x40000)] + struct SharedMemory + { + /// <summary> + /// Debug controller. + /// </summary> + [FieldOffset(0)] + public RingLifo<DebugPadState> DebugPad; + + /// <summary> + /// Touchscreen. + /// </summary> + [FieldOffset(0x400)] + public RingLifo<TouchScreenState> TouchScreen; + + /// <summary> + /// Mouse. + /// </summary> + [FieldOffset(0x3400)] + public RingLifo<MouseState> Mouse; + + /// <summary> + /// Keyboard. + /// </summary> + [FieldOffset(0x3800)] + public RingLifo<KeyboardState> Keyboard; + + /// <summary> + /// Nintendo Pads. + /// </summary> + [FieldOffset(0x9A00)] + public Array10<NpadState> Npads; + + public static SharedMemory Create() + { + SharedMemory result = new SharedMemory + { + DebugPad = RingLifo<DebugPadState>.Create(), + TouchScreen = RingLifo<TouchScreenState>.Create(), + Mouse = RingLifo<MouseState>.Create(), + Keyboard = RingLifo<KeyboardState>.Create(), + }; + + for (int i = 0; i < result.Npads.Length; i++) + { + result.Npads[i] = NpadState.Create(); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs new file mode 100644 index 00000000..d2c5726a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + [Flags] + public enum TouchAttribute : uint + { + None = 0, + Start = 1 << 0, + End = 1 << 1 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs new file mode 100644 index 00000000..cdd4cc45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TouchScreenState : ISampledDataStruct + { + public ulong SamplingNumber; + public int TouchesCount; + private int _reserved; + public Array16<TouchState> Touches; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs new file mode 100644 index 00000000..ba621a2b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + struct TouchState + { + public ulong DeltaTime; +#pragma warning disable CS0649 + public TouchAttribute Attribute; +#pragma warning restore CS0649 + public uint FingerId; + public uint X; + public uint Y; + public uint DiameterX; + public uint DiameterY; + public uint RotationAngle; +#pragma warning disable CS0169 + private uint _reserved; +#pragma warning restore CS0169 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs b/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs new file mode 100644 index 00000000..34d4bdfd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ins +{ + [Service("ins:r")] + class IReceiverManager : IpcService + { + public IReceiverManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs b/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs new file mode 100644 index 00000000..38a95ee7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ins +{ + [Service("ins:s")] + class ISenderManager : IpcService + { + public ISenderManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs new file mode 100644 index 00000000..048a68a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -0,0 +1,284 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services +{ + abstract class IpcService + { + public IReadOnlyDictionary<int, MethodInfo> CmifCommands { get; } + public IReadOnlyDictionary<int, MethodInfo> TipcCommands { get; } + + public ServerBase Server { get; private set; } + + private IpcService _parent; + private IdDictionary _domainObjects; + private int _selfId; + private bool _isDomain; + + public IpcService(ServerBase server = null) + { + CmifCommands = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => type == GetType()) + .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandCmifAttribute)) + .Select(command => (((CommandCmifAttribute)command).Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + TipcCommands = Assembly.GetExecutingAssembly().GetTypes() + .Where(type => type == GetType()) + .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandTipcAttribute)) + .Select(command => (((CommandTipcAttribute)command).Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + Server = server; + + _parent = this; + _domainObjects = new IdDictionary(); + _selfId = -1; + } + + public int ConvertToDomain() + { + if (_selfId == -1) + { + _selfId = _domainObjects.Add(this); + } + + _isDomain = true; + + return _selfId; + } + + public void ConvertToSession() + { + _isDomain = false; + } + + public void CallCmifMethod(ServiceCtx context) + { + IpcService service = this; + + if (_isDomain) + { + int domainWord0 = context.RequestData.ReadInt32(); + int domainObjId = context.RequestData.ReadInt32(); + + int domainCmd = (domainWord0 >> 0) & 0xff; + int inputObjCount = (domainWord0 >> 8) & 0xff; + int dataPayloadSize = (domainWord0 >> 16) & 0xffff; + + context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin); + + context.Request.ObjectIds.EnsureCapacity(inputObjCount); + + for (int index = 0; index < inputObjCount; index++) + { + context.Request.ObjectIds.Add(context.RequestData.ReadInt32()); + } + + context.RequestData.BaseStream.Seek(0x10, SeekOrigin.Begin); + + if (domainCmd == 1) + { + service = GetObject(domainObjId); + + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + } + else if (domainCmd == 2) + { + Delete(domainObjId); + + context.ResponseData.Write(0L); + + return; + } + else + { + throw new NotImplementedException($"Domain command: {domainCmd}"); + } + } + + long sfciMagic = context.RequestData.ReadInt64(); + int commandId = (int)context.RequestData.ReadInt64(); + + bool serviceExists = service.CmifCommands.TryGetValue(commandId, out MethodInfo processRequest); + + if (context.Device.Configuration.IgnoreMissingServices || serviceExists) + { + ResultCode result = ResultCode.Success; + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x20 : 0x10, SeekOrigin.Begin); + + if (serviceExists) + { + Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); + + result = (ResultCode)processRequest.Invoke(service, new object[] { context }); + } + else + { + string serviceName; + + DummyService dummyService = service as DummyService; + + serviceName = (dummyService == null) ? service.GetType().FullName : dummyService.ServiceName; + + Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); + } + + if (_isDomain) + { + foreach (int id in context.Response.ObjectIds) + { + context.ResponseData.Write(id); + } + + context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + context.ResponseData.Write(context.Response.ObjectIds.Count); + } + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x10 : 0, SeekOrigin.Begin); + + context.ResponseData.Write(IpcMagic.Sfco); + context.ResponseData.Write((long)result); + } + else + { + string dbgMessage = $"{service.GetType().FullName}: {commandId}"; + + throw new ServiceNotImplementedException(service, context, dbgMessage); + } + } + + public void CallTipcMethod(ServiceCtx context) + { + int commandId = (int)context.Request.Type - 0x10; + + bool serviceExists = TipcCommands.TryGetValue(commandId, out MethodInfo processRequest); + + if (context.Device.Configuration.IgnoreMissingServices || serviceExists) + { + ResultCode result = ResultCode.Success; + + context.ResponseData.BaseStream.Seek(0x4, SeekOrigin.Begin); + + if (serviceExists) + { + Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); + + result = (ResultCode)processRequest.Invoke(this, new object[] { context }); + } + else + { + string serviceName; + + DummyService dummyService = this as DummyService; + + serviceName = (dummyService == null) ? GetType().FullName : dummyService.ServiceName; + + Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); + } + + context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + context.ResponseData.Write((uint)result); + } + else + { + string dbgMessage = $"{GetType().FullName}: {commandId}"; + + throw new ServiceNotImplementedException(this, context, dbgMessage); + } + } + + protected void MakeObject(ServiceCtx context, IpcService obj) + { + obj.TrySetServer(_parent.Server); + + if (_parent._isDomain) + { + obj._parent = _parent; + + context.Response.ObjectIds.Add(_parent.Add(obj)); + } + else + { + context.Device.System.KernelContext.Syscall.CreateSession(out int serverSessionHandle, out int clientSessionHandle, false, 0); + + obj.Server.AddSessionObj(serverSessionHandle, obj); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(clientSessionHandle); + } + } + + protected T GetObject<T>(ServiceCtx context, int index) where T : IpcService + { + int objId = context.Request.ObjectIds[index]; + + IpcService obj = _parent.GetObject(objId); + + return obj is T t ? t : null; + } + + public bool TrySetServer(ServerBase newServer) + { + if (Server == null) + { + Server = newServer; + + return true; + } + + return false; + } + + private int Add(IpcService obj) + { + return _domainObjects.Add(obj); + } + + private bool Delete(int id) + { + object obj = _domainObjects.Delete(id); + + if (obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + return obj != null; + } + + private IpcService GetObject(int id) + { + return _domainObjects.GetData<IpcService>(id); + } + + public void SetParent(IpcService parent) + { + _parent = parent._parent; + } + + public virtual void DestroyAtExit() + { + foreach (object domainObject in _domainObjects.Values) + { + if (domainObject != this && domainObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + } + + _domainObjects.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs b/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs new file mode 100644 index 00000000..65074d5f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs @@ -0,0 +1,92 @@ +namespace Ryujinx.HLE.HOS.Services.Lbl +{ + abstract class ILblController : IpcService + { + public ILblController(ServiceCtx context) { } + + protected abstract void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode); + protected abstract float GetCurrentBrightnessSettingForVrMode(); + internal abstract void EnableVrMode(); + internal abstract void DisableVrMode(); + protected abstract bool IsVrModeEnabled(); + + [CommandCmif(17)] + // SetBrightnessReflectionDelayLevel(float, float) + public ResultCode SetBrightnessReflectionDelayLevel(ServiceCtx context) + { + return ResultCode.Success; + } + + [CommandCmif(18)] + // GetBrightnessReflectionDelayLevel(float) -> float + public ResultCode GetBrightnessReflectionDelayLevel(ServiceCtx context) + { + context.ResponseData.Write(0.0f); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // SetCurrentAmbientLightSensorMapping(unknown<0xC>) + public ResultCode SetCurrentAmbientLightSensorMapping(ServiceCtx context) + { + return ResultCode.Success; + } + + [CommandCmif(22)] + // GetCurrentAmbientLightSensorMapping() -> unknown<0xC> + public ResultCode GetCurrentAmbientLightSensorMapping(ServiceCtx context) + { + return ResultCode.Success; + } + + [CommandCmif(24)] // 3.0.0+ + // SetCurrentBrightnessSettingForVrMode(float) + public ResultCode SetCurrentBrightnessSettingForVrMode(ServiceCtx context) + { + float currentBrightnessSettingForVrMode = context.RequestData.ReadSingle(); + + SetCurrentBrightnessSettingForVrMode(currentBrightnessSettingForVrMode); + + return ResultCode.Success; + } + + [CommandCmif(25)] // 3.0.0+ + // GetCurrentBrightnessSettingForVrMode() -> float + public ResultCode GetCurrentBrightnessSettingForVrMode(ServiceCtx context) + { + float currentBrightnessSettingForVrMode = GetCurrentBrightnessSettingForVrMode(); + + context.ResponseData.Write(currentBrightnessSettingForVrMode); + + return ResultCode.Success; + } + + [CommandCmif(26)] // 3.0.0+ + // EnableVrMode() + public ResultCode EnableVrMode(ServiceCtx context) + { + EnableVrMode(); + + return ResultCode.Success; + } + + [CommandCmif(27)] // 3.0.0+ + // DisableVrMode() + public ResultCode DisableVrMode(ServiceCtx context) + { + DisableVrMode(); + + return ResultCode.Success; + } + + [CommandCmif(28)] // 3.0.0+ + // IsVrModeEnabled() -> bool + public ResultCode IsVrModeEnabled(ServiceCtx context) + { + context.ResponseData.Write(IsVrModeEnabled()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs b/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs new file mode 100644 index 00000000..b68be1f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs @@ -0,0 +1,54 @@ +namespace Ryujinx.HLE.HOS.Services.Lbl +{ + [Service("lbl")] + class LblControllerServer : ILblController + { + private bool _vrModeEnabled; + private float _currentBrightnessSettingForVrMode; + + public LblControllerServer(ServiceCtx context) : base(context) { } + + protected override void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode) + { + if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode)) + { + _currentBrightnessSettingForVrMode = 0.0f; + + return; + } + + _currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode; + } + + protected override float GetCurrentBrightnessSettingForVrMode() + { + if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode)) + { + return 0.0f; + } + + return _currentBrightnessSettingForVrMode; + } + + internal override void EnableVrMode() + { + _vrModeEnabled = true; + + // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness. + // Since we don't support that. It's fine to do nothing. + } + + internal override void DisableVrMode() + { + _vrModeEnabled = false; + + // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness. + // Since we don't support that. It's fine to do nothing. + } + + protected override bool IsVrModeEnabled() + { + return _vrModeEnabled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs new file mode 100644 index 00000000..09dfa78f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:m")] + class IMonitorServiceCreator : IpcService + { + public IMonitorServiceCreator(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs new file mode 100644 index 00000000..b4dac449 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:s")] + class ISystemServiceCreator : IpcService + { + public ISystemServiceCreator(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs new file mode 100644 index 00000000..4f3094ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:u")] + class IUserServiceCreator : IpcService + { + public IUserServiceCreator(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService> + public ResultCode CreateUserLocalCommunicationService(ServiceCtx context) + { + MakeObject(context, new IUserLocalCommunicationService(context)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs new file mode 100644 index 00000000..9c9ee3be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p +{ + [Service("lp2p:app")] // 9.0.0+ + [Service("lp2p:sys")] // 9.0.0+ + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs new file mode 100644 index 00000000..274b6132 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + internal class NetworkInterface + { + public ResultCode NifmState { get; set; } + public KEvent StateChangeEvent { get; private set; } + + private NetworkState _state; + + public NetworkInterface(Horizon system) + { + // TODO(Ac_K): Determine where the internal state is set. + NifmState = ResultCode.Success; + StateChangeEvent = new KEvent(system.KernelContext); + + _state = NetworkState.None; + } + + public ResultCode Initialize(int unknown, int version, IPAddress ipv4Address, IPAddress subnetMaskAddress) + { + // TODO(Ac_K): Call nn::nifm::InitializeSystem(). + // If the call failed, it returns the result code. + // If the call succeed, it signal and clear an event then start a new thread named nn.ldn.NetworkInterfaceMonitor. + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { version }); + + // NOTE: Since we don't support ldn for now, we can return this following result code to make it disabled. + return ResultCode.DeviceDisabled; + } + + public ResultCode GetState(out NetworkState state) + { + // Return ResultCode.InvalidArgument if _state is null, doesn't occur in our case. + + state = _state; + + return ResultCode.Success; + } + + public ResultCode Finalize() + { + // TODO(Ac_K): Finalize nifm service then kill the thread named nn.ldn.NetworkInterfaceMonitor. + + _state = NetworkState.None; + + StateChangeEvent.WritableEvent.Signal(); + StateChangeEvent.WritableEvent.Clear(); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs new file mode 100644 index 00000000..87674f7c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + enum ResultCode + { + ModuleId = 203, + ErrorCodeShift = 9, + + Success = 0, + + DeviceDisabled = (22 << ErrorCodeShift) | ModuleId, + InvalidState = (32 << ErrorCodeShift) | ModuleId, + Unknown1 = (48 << ErrorCodeShift) | ModuleId, + InvalidArgument = (96 << ErrorCodeShift) | ModuleId, + InvalidObject = (97 << ErrorCodeShift) | ModuleId, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs new file mode 100644 index 00000000..6ac20483 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum NetworkState + { + None, + Initialized, + AccessPoint, + AccessPointCreated, + Station, + StationConnected, + Error + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs new file mode 100644 index 00000000..f425ddf7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -0,0 +1,88 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.Horizon.Common; +using System; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class IUserLocalCommunicationService : IpcService + { + // TODO(Ac_K): Determine what the hardcoded unknown value is. + private const int UnknownValue = 90; + + private NetworkInterface _networkInterface; + + private int _stateChangeEventHandle = 0; + + public IUserLocalCommunicationService(ServiceCtx context) + { + _networkInterface = new NetworkInterface(context.Device.System); + } + + [CommandCmif(0)] + // GetState() -> s32 state + public ResultCode GetState(ServiceCtx context) + { + if (_networkInterface.NifmState != ResultCode.Success) + { + context.ResponseData.Write((int)NetworkState.Error); + + return ResultCode.Success; + } + + ResultCode result = _networkInterface.GetState(out NetworkState state); + + if (result == ResultCode.Success) + { + context.ResponseData.Write((int)state); + } + + return result; + } + + [CommandCmif(100)] + // AttachStateChangeEvent() -> handle<copy> + public ResultCode AttachStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_networkInterface.StateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + // Return ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception. + + return ResultCode.Success; + } + + [CommandCmif(400)] + // InitializeOld(u64, pid) + public ResultCode InitializeOld(ServiceCtx context) + { + return _networkInterface.Initialize(UnknownValue, 0, null, null); + } + + [CommandCmif(401)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + return _networkInterface.Finalize(); + } + + [CommandCmif(402)] // 7.0.0+ + // Initialize(u64 ip_addresses, u64, pid) + public ResultCode Initialize(ServiceCtx context) + { + // TODO(Ac_K): Determine what addresses are. + IPAddress unknownAddress1 = new IPAddress(context.RequestData.ReadUInt32()); + IPAddress unknownAddress2 = new IPAddress(context.RequestData.ReadUInt32()); + + return _networkInterface.Initialize(UnknownValue, version: 1, unknownAddress1, unknownAddress2); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs new file mode 100644 index 00000000..82b24a35 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs new file mode 100644 index 00000000..2ecde2ad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:pm")] + class IProcessManagerInterface : IpcService + { + public IProcessManagerInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs new file mode 100644 index 00000000..362f82f0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:shel")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs new file mode 100644 index 00000000..170dfa01 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + enum ResultCode + { + ModuleId = 9, + ErrorCodeShift = 9, + + Success = 0, + + ArgsTooLong = (1 << ErrorCodeShift) | ModuleId, + MaximumProcessesLoaded = (2 << ErrorCodeShift) | ModuleId, + NPDMTooBig = (3 << ErrorCodeShift) | ModuleId, + InvalidNPDM = (4 << ErrorCodeShift) | ModuleId, + InvalidNSO = (5 << ErrorCodeShift) | ModuleId, + InvalidPath = (6 << ErrorCodeShift) | ModuleId, + AlreadyRegistered = (7 << ErrorCodeShift) | ModuleId, + TitleNotFound = (8 << ErrorCodeShift) | ModuleId, + ACI0TitleIdNotMatchingRangeInACID = (9 << ErrorCodeShift) | ModuleId, + InvalidVersionInNPDM = (10 << ErrorCodeShift) | ModuleId, + InsufficientAddressSpace = (51 << ErrorCodeShift) | ModuleId, + InsufficientNRO = (52 << ErrorCodeShift) | ModuleId, + InvalidNRR = (53 << ErrorCodeShift) | ModuleId, + InvalidSignature = (54 << ErrorCodeShift) | ModuleId, + InsufficientNRORegistrations = (55 << ErrorCodeShift) | ModuleId, + InsufficientNRRRegistrations = (56 << ErrorCodeShift) | ModuleId, + NROAlreadyLoaded = (57 << ErrorCodeShift) | ModuleId, + UnalignedNRRAddress = (81 << ErrorCodeShift) | ModuleId, + BadNRRSize = (82 << ErrorCodeShift) | ModuleId, + NRRNotLoaded = (84 << ErrorCodeShift) | ModuleId, + BadNRRAddress = (85 << ErrorCodeShift) | ModuleId, + BadInitialization = (87 << ErrorCodeShift) | ModuleId, + UnknownACI0Descriptor = (100 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingKernelFlagsDescriptor = (103 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingSyscallMaskDescriptor = (104 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingMapIoOrNormalRangeDescriptor = (106 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingMapNormalPageDescriptor = (107 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingInterruptPairDescriptor = (111 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingApplicationTypeDescriptor = (113 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingKernelReleaseVersionDescriptor = (114 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingHandleTableSizeDescriptor = (115 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingDebugFlagsDescriptor = (116 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs b/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs new file mode 100644 index 00000000..2f6eb99e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Mig +{ + [Service("mig:usr")] // 4.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs new file mode 100644 index 00000000..6d65de95 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs @@ -0,0 +1,328 @@ +using LibHac; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class DatabaseImpl + { + private static DatabaseImpl _instance; + + public static DatabaseImpl Instance + { + get + { + if (_instance == null) + { + _instance = new DatabaseImpl(); + } + + return _instance; + } + } + + private UtilityImpl _utilityImpl; + private MiiDatabaseManager _miiDatabase; + private bool _isBroken; + + public DatabaseImpl() + { + _miiDatabase = new MiiDatabaseManager(); + } + + public bool IsUpdated(DatabaseSessionMetadata metadata, SourceFlag flag) + { + if (flag.HasFlag(SourceFlag.Database)) + { + return _miiDatabase.IsUpdated(metadata); + } + + return false; + } + + public bool IsBrokenDatabaseWithClearFlag() + { + bool result = _isBroken; + + if (_isBroken) + { + _isBroken = false; + + Format(new DatabaseSessionMetadata(0, new SpecialMiiKeyCode())); + } + + return result; + } + + public bool IsFullDatabase() + { + return _miiDatabase.IsFullDatabase(); + } + + private ResultCode GetDefault<T>(SourceFlag flag, ref int count, Span<T> elements) where T : struct, IElement + { + if (!flag.HasFlag(SourceFlag.Default)) + { + return ResultCode.Success; + } + + for (uint i = 0; i < DefaultMii.TableLength; i++) + { + if (count >= elements.Length) + { + return ResultCode.BufferTooSmall; + } + + elements[count] = default; + elements[count].SetFromStoreData(StoreData.BuildDefault(_utilityImpl, i)); + elements[count].SetSource(Source.Default); + + count++; + } + + return ResultCode.Success; + } + + public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged + { + if (!flag.HasFlag(SourceFlag.Database)) + { + return ResultCode.NotFound; + } + + if (metadata.IsInterfaceVersionSupported(1) && !oldMiiData.IsValid()) + { + return oldMiiData.InvalidData; + } + + ResultCode result = _miiDatabase.FindIndex(metadata, out int index, oldMiiData.CreateId); + + if (result == ResultCode.Success) + { + _miiDatabase.Get(metadata, index, out StoreData storeData); + + if (storeData.Type != oldMiiData.Type) + { + return ResultCode.NotFound; + } + + newMiiData.SetFromStoreData(storeData); + + if (oldMiiData == newMiiData) + { + return ResultCode.NotUpdated; + } + } + + return result; + } + + public ResultCode Get<T>(DatabaseSessionMetadata metadata, SourceFlag flag, out int count, Span<T> elements) where T : struct, IElement + { + count = 0; + + if (!flag.HasFlag(SourceFlag.Database)) + { + return GetDefault(flag, ref count, elements); + } + + int databaseCount = _miiDatabase.GetCount(metadata); + + for (int i = 0; i < databaseCount; i++) + { + if (count >= elements.Length) + { + return ResultCode.BufferTooSmall; + } + + _miiDatabase.Get(metadata, i, out StoreData storeData); + + elements[count] = default; + elements[count].SetFromStoreData(storeData); + elements[count].SetSource(Source.Database); + + count++; + } + + return GetDefault(flag, ref count, elements); + } + + public ResultCode InitializeDatabase(ITickSource tickSource, HorizonClient horizonClient) + { + _utilityImpl = new UtilityImpl(tickSource); + _miiDatabase.InitializeDatabase(horizonClient); + _miiDatabase.LoadFromFile(out _isBroken); + + // Nintendo ignores any error code from before. + return ResultCode.Success; + } + + public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode) + { + return _miiDatabase.CreateSessionMetadata(miiKeyCode); + } + + public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion) + { + _miiDatabase.SetInterfaceVersion(metadata, interfaceVersion); + } + + public void Format(DatabaseSessionMetadata metadata) + { + _miiDatabase.FormatDatabase(metadata); + _miiDatabase.SaveDatabase(); + } + + public ResultCode DestroyFile(DatabaseSessionMetadata metadata) + { + _isBroken = true; + + return _miiDatabase.DestroyFile(metadata); + } + + public void BuildDefault(uint index, out CharInfo charInfo) + { + StoreData storeData = StoreData.BuildDefault(_utilityImpl, index); + + charInfo = default; + + charInfo.SetFromStoreData(storeData); + } + + public void BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo) + { + StoreData storeData = StoreData.BuildRandom(_utilityImpl, age, gender, race); + + charInfo = default; + + charInfo.SetFromStoreData(storeData); + } + + public ResultCode DeleteFile() + { + return _miiDatabase.DeleteFile(); + } + + public ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo) + { + charInfo = new CharInfo(); + + if (!coreData.IsValid()) + { + return ResultCode.InvalidCoreData; + } + + StoreData storeData = StoreData.BuildFromCoreData(_utilityImpl, coreData); + + if (!storeData.CoreData.Nickname.IsValidForFontRegion(storeData.CoreData.FontRegion)) + { + storeData.CoreData.Nickname = Nickname.Question; + storeData.UpdateCrc(); + } + + charInfo.SetFromStoreData(storeData); + + return ResultCode.Success; + } + + public int FindIndex(CreateId createId, bool isSpecial) + { + if (_miiDatabase.FindIndex(out int index, createId, isSpecial) == ResultCode.Success) + { + return index; + } + + return -1; + } + + public uint GetCount(DatabaseSessionMetadata metadata, SourceFlag flag) + { + int count = 0; + + if (flag.HasFlag(SourceFlag.Default)) + { + count += DefaultMii.TableLength; + } + + if (flag.HasFlag(SourceFlag.Database)) + { + count += _miiDatabase.GetCount(metadata); + } + + return (uint)count; + } + + public ResultCode Move(DatabaseSessionMetadata metadata, int index, CreateId createId) + { + ResultCode result = _miiDatabase.Move(metadata, index, createId); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId) + { + ResultCode result = _miiDatabase.Delete(metadata, createId); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData) + { + ResultCode result = _miiDatabase.AddOrReplace(metadata, storeData); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData) + { + coreData = new CoreData(); + + if (charInfo.IsValid()) + { + return ResultCode.InvalidCharInfo; + } + + coreData.SetFromCharInfo(charInfo); + + if (!coreData.Nickname.IsValidForFontRegion(coreData.FontRegion)) + { + coreData.Nickname = Nickname.Question; + } + + return ResultCode.Success; + } + + public ResultCode GetIndex(DatabaseSessionMetadata metadata, CharInfo charInfo, out int index) + { + if (!charInfo.IsValid()) + { + index = -1; + + return ResultCode.InvalidCharInfo; + } + + if (_miiDatabase.FindIndex(out index, charInfo.CreateId, metadata.MiiKeyCode.IsEnabledSpecialMii()) != ResultCode.Success) + { + return ResultCode.NotFound; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs new file mode 100644 index 00000000..6982b0ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Mii.Types; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class DatabaseSessionMetadata + { + public uint InterfaceVersion; + public ulong UpdateCounter; + + public SpecialMiiKeyCode MiiKeyCode { get; private set; } + + public DatabaseSessionMetadata(ulong updateCounter, SpecialMiiKeyCode miiKeyCode) + { + InterfaceVersion = 0; + UpdateCounter = updateCounter; + MiiKeyCode = miiKeyCode; + } + + public bool IsInterfaceVersionSupported(uint interfaceVersion) + { + return InterfaceVersion >= interfaceVersion; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs new file mode 100644 index 00000000..b02bbfd1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs @@ -0,0 +1,48 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Buffers.Binary; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + static class Helper + { + public static ushort CalculateCrc16(ReadOnlySpan<byte> data, int crc, bool reverseEndianess) + { + const ushort poly = 0x1021; + + for (int i = 0; i < data.Length; i++) + { + crc ^= data[i] << 8; + + for (int j = 0; j < 8; j++) + { + crc <<= 1; + + if ((crc & 0x10000) != 0) + { + crc = (crc ^ poly) & 0xFFFF; + } + } + } + + if (reverseEndianess) + { + return (ushort)(BinaryPrimitives.ReverseEndianness(crc) >> 16); + } + + return (ushort)crc; + } + + public static UInt128 GetDeviceId() + { + // FIXME: call set:sys GetMiiAuthorId + return UInt128Utils.FromHex("5279754d69694e780000000000000000"); // RyuMiiNx + } + + public static ReadOnlySpan<byte> Ver3FacelineColorTable => new byte[] { 0, 1, 2, 3, 4, 5 }; + public static ReadOnlySpan<byte> Ver3HairColorTable => new byte[] { 8, 1, 2, 3, 4, 5, 6, 7 }; + public static ReadOnlySpan<byte> Ver3EyeColorTable => new byte[] { 8, 9, 10, 11, 12, 13 }; + public static ReadOnlySpan<byte> Ver3MouthColorTable => new byte[] { 19, 20, 21, 22, 23 }; + public static ReadOnlySpan<byte> Ver3GlassColorTable => new byte[] { 8, 14, 15, 16, 17, 18, 0 }; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs new file mode 100644 index 00000000..7d65c73f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs @@ -0,0 +1,41 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + [Service("miiimg")] // 5.0.0+ + class IImageDatabaseService : IpcService + { + private uint _imageCount; + private bool _isDirty; + + public IImageDatabaseService(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(b8) -> b8 + public ResultCode Initialize(ServiceCtx context) + { + // TODO: Service uses MiiImage:/database.dat if true, seems to use hardcoded data if false. + bool useHardcodedData = context.RequestData.ReadBoolean(); + + _imageCount = 0; + _isDirty = false; + + context.ResponseData.Write(_isDirty); + + Logger.Stub?.PrintStub(LogClass.ServiceMii, new { useHardcodedData }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetCount() -> u32 + public ResultCode GetCount(ServiceCtx context) + { + context.ResponseData.Write(_imageCount); + + Logger.Stub?.PrintStub(LogClass.ServiceMii); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs new file mode 100644 index 00000000..a7fc71c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Mii.StaticService; +using Ryujinx.HLE.HOS.Services.Mii.Types; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + [Service("mii:e", true)] + [Service("mii:u", false)] + class IStaticService : IpcService + { + private DatabaseImpl _databaseImpl; + + private bool _isSystem; + + public IStaticService(ServiceCtx context, bool isSystem) + { + _isSystem = isSystem; + _databaseImpl = DatabaseImpl.Instance; + } + + [CommandCmif(0)] + // GetDatabaseService(u32 mii_key_code) -> object<nn::mii::detail::IDatabaseService> + public ResultCode GetDatabaseService(ServiceCtx context) + { + SpecialMiiKeyCode miiKeyCode = context.RequestData.ReadStruct<SpecialMiiKeyCode>(); + + MakeObject(context, new DatabaseServiceImpl(_databaseImpl, _isSystem, miiKeyCode)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs new file mode 100644 index 00000000..682283b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs @@ -0,0 +1,501 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class MiiDatabaseManager + { + private static bool IsTestModeEnabled = false; + private static uint MountCounter = 0; + + private const ulong DatabaseTestSaveDataId = 0x8000000000000031; + private const ulong DatabaseSaveDataId = 0x8000000000000030; + + private static U8String DatabasePath = new U8String("mii:/MiiDatabase.dat"); + private static U8String MountName = new U8String("mii"); + + private NintendoFigurineDatabase _database; + private bool _isDirty; + + private HorizonClient _horizonClient; + + protected ulong UpdateCounter { get; private set; } + + public MiiDatabaseManager() + { + _database = new NintendoFigurineDatabase(); + _isDirty = false; + UpdateCounter = 0; + } + + private void ResetDatabase() + { + _database = new NintendoFigurineDatabase(); + _database.Format(); + } + + private void MarkDirty(DatabaseSessionMetadata metadata) + { + _isDirty = true; + + UpdateCounter++; + + metadata.UpdateCounter = UpdateCounter; + } + + private bool GetAtVirtualIndex(int index, out int realIndex, out StoreData storeData) + { + realIndex = -1; + storeData = new StoreData(); + + int virtualIndex = 0; + + for (int i = 0; i < _database.Length; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + if (index == virtualIndex) + { + realIndex = i; + storeData = tmp; + + return true; + } + + virtualIndex++; + } + } + + return false; + } + + private int ConvertRealIndexToVirtualIndex(int realIndex) + { + int virtualIndex = 0; + + for (int i = 0; i < realIndex; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + virtualIndex++; + } + } + + return virtualIndex; + } + + public void InitializeDatabase(HorizonClient horizonClient) + { + _horizonClient = horizonClient; + + // Ensure we have valid data in the database + _database.Format(); + + MountSave(); + } + + private Result MountSave() + { + if (MountCounter != 0) + { + MountCounter++; + return Result.Success; + } + + ulong saveDataId = IsTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId; + + Result result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId); + + if (result.IsFailure()) + { + if (!ResultFs.TargetNotFound.Includes(result)) + return result; + + if (IsTestModeEnabled) + { + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000, + SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) return result; + } + else + { + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000, + 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) return result; + } + + result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId); + if (result.IsFailure()) return result; + } + + if (result == Result.Success) + { + MountCounter++; + } + return result; + } + + public ResultCode DeleteFile() + { + ResultCode result = (ResultCode)_horizonClient.Fs.DeleteFile(DatabasePath).Value; + + _horizonClient.Fs.Commit(MountName); + + return result; + } + + public ResultCode LoadFromFile(out bool isBroken) + { + isBroken = false; + + if (MountCounter == 0) + { + return ResultCode.InvalidArgument; + } + + UpdateCounter++; + + ResetDatabase(); + + Result result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); + + if (result.IsSuccess()) + { + if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>()) + { + result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan()); + + if (result.IsSuccess()) + { + if (_database.Verify() != ResultCode.Success) + { + ResetDatabase(); + + isBroken = true; + } + else + { + isBroken = _database.FixDatabase(); + } + } + } + else + { + isBroken = true; + } + } + + _horizonClient.Fs.CloseFile(handle); + + return (ResultCode)result.Value; + } + else if (ResultFs.PathNotFound.Includes(result)) + { + return (ResultCode)ForceSaveDatabase().Value; + } + + return ResultCode.Success; + } + + private Result ForceSaveDatabase() + { + Result result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>()); + + if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result)) + { + result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); + + if (result.IsSuccess()) + { + // If the size doesn't match, recreate the file + if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>()) + { + _horizonClient.Fs.CloseFile(handle); + + result = _horizonClient.Fs.DeleteFile(DatabasePath); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>()); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.OpenFile(out handle, DatabasePath, OpenMode.Write); + } + } + + if (result.IsFailure()) + { + return result; + } + } + + result = _horizonClient.Fs.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush); + } + + _horizonClient.Fs.CloseFile(handle); + } + } + + if (result.IsSuccess()) + { + _isDirty = false; + + result = _horizonClient.Fs.Commit(MountName); + } + + return result; + } + + public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode) + { + return new DatabaseSessionMetadata(UpdateCounter, miiKeyCode); + } + + public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion) + { + metadata.InterfaceVersion = interfaceVersion; + } + + public bool IsUpdated(DatabaseSessionMetadata metadata) + { + bool result = metadata.UpdateCounter != UpdateCounter; + + metadata.UpdateCounter = UpdateCounter; + + return result; + } + + public int GetCount(DatabaseSessionMetadata metadata) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + int count = 0; + + for (int i = 0; i < _database.Length; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + count++; + } + } + + return count; + } + else + { + return _database.Length; + } + } + + public void Get(DatabaseSessionMetadata metadata, int index, out StoreData storeData) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + if (GetAtVirtualIndex(index, out int realIndex, out _)) + { + index = realIndex; + } + else + { + index = 0; + } + } + + storeData = _database.Get(index); + } + + public ResultCode FindIndex(DatabaseSessionMetadata metadata, out int index, CreateId createId) + { + return FindIndex(out index, createId, metadata.MiiKeyCode.IsEnabledSpecialMii()); + } + + public ResultCode FindIndex(out int index, CreateId createId, bool isSpecial) + { + if (_database.GetIndexByCreatorId(out int realIndex, createId)) + { + if (isSpecial) + { + index = realIndex; + + return ResultCode.Success; + } + + StoreData storeData = _database.Get(realIndex); + + if (!storeData.IsSpecial()) + { + if (realIndex < 1) + { + index = 0; + } + else + { + index = ConvertRealIndexToVirtualIndex(realIndex); + } + + return ResultCode.Success; + } + } + + index = -1; + + return ResultCode.NotFound; + } + + public ResultCode Move(DatabaseSessionMetadata metadata, int newIndex, CreateId createId) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + if (GetAtVirtualIndex(newIndex, out int realIndex, out _)) + { + newIndex = realIndex; + } + else + { + newIndex = 0; + } + } + + if (_database.GetIndexByCreatorId(out int oldIndex, createId)) + { + StoreData realStoreData = _database.Get(oldIndex); + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && realStoreData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + ResultCode result = _database.Move(newIndex, oldIndex); + + if (result == ResultCode.Success) + { + MarkDirty(metadata); + } + + return result; + } + + return ResultCode.NotFound; + } + + public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData) + { + if (!storeData.IsValid()) + { + return ResultCode.InvalidStoreData; + } + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && storeData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + if (_database.GetIndexByCreatorId(out int index, storeData.CreateId)) + { + StoreData oldStoreData = _database.Get(index); + + if (oldStoreData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + _database.Replace(index, storeData); + } + else + { + if (_database.IsFull()) + { + return ResultCode.DatabaseFull; + } + + _database.Add(storeData); + } + + MarkDirty(metadata); + + return ResultCode.Success; + } + + public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId) + { + if (!_database.GetIndexByCreatorId(out int index, createId)) + { + return ResultCode.NotFound; + } + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + StoreData storeData = _database.Get(index); + + if (storeData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + } + + _database.Delete(index); + + MarkDirty(metadata); + + return ResultCode.Success; + } + + public ResultCode DestroyFile(DatabaseSessionMetadata metadata) + { + _database.CorruptDatabase(); + + MarkDirty(metadata); + + ResultCode result = SaveDatabase(); + + ResetDatabase(); + + return result; + } + + public ResultCode SaveDatabase() + { + if (_isDirty) + { + return (ResultCode)ForceSaveDatabase().Value; + } + else + { + return ResultCode.NotUpdated; + } + } + + public void FormatDatabase(DatabaseSessionMetadata metadata) + { + _database.Format(); + + MarkDirty(metadata); + } + + public bool IsFullDatabase() + { + return _database.IsFull(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs new file mode 100644 index 00000000..4a4c0c23 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Mii +{ + public enum ResultCode + { + ModuleId = 126, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (1 << ErrorCodeShift) | ModuleId, + BufferTooSmall = (2 << ErrorCodeShift) | ModuleId, + NotUpdated = (3 << ErrorCodeShift) | ModuleId, + NotFound = (4 << ErrorCodeShift) | ModuleId, + DatabaseFull = (5 << ErrorCodeShift) | ModuleId, + InvalidDatabaseSignatureValue = (67 << ErrorCodeShift) | ModuleId, + InvalidDatabaseEntryCount = (69 << ErrorCodeShift) | ModuleId, + InvalidCharInfo = (100 << ErrorCodeShift) | ModuleId, + InvalidCrc = (101 << ErrorCodeShift) | ModuleId, + InvalidDeviceCrc = (102 << ErrorCodeShift) | ModuleId, + InvalidDatabaseMagic = (103 << ErrorCodeShift) | ModuleId, + InvalidDatabaseVersion = (104 << ErrorCodeShift) | ModuleId, + InvalidDatabaseSize = (105 << ErrorCodeShift) | ModuleId, + InvalidCreateId = (106 << ErrorCodeShift) | ModuleId, + InvalidCoreData = (108 << ErrorCodeShift) | ModuleId, + InvalidStoreData = (109 << ErrorCodeShift) | ModuleId, + InvalidOperationOnSpecialMii = (202 << ErrorCodeShift) | ModuleId, + PermissionDenied = (203 << ErrorCodeShift) | ModuleId, + TestModeNotEnabled = (204 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs new file mode 100644 index 00000000..4b5ed0d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs @@ -0,0 +1,266 @@ +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.StaticService +{ + class DatabaseServiceImpl : IDatabaseService + { + private DatabaseImpl _database; + private DatabaseSessionMetadata _metadata; + private bool _isSystem; + + public DatabaseServiceImpl(DatabaseImpl database, bool isSystem, SpecialMiiKeyCode miiKeyCode) + { + _database = database; + _metadata = _database.CreateSessionMetadata(miiKeyCode); + _isSystem = isSystem; + } + + public bool IsDatabaseTestModeEnabled() + { + if (NxSettings.Settings.TryGetValue("mii!is_db_test_mode_enabled", out object isDatabaseTestModeEnabled)) + { + return (bool)isDatabaseTestModeEnabled; + } + + return false; + } + + protected override bool IsUpdated(SourceFlag flag) + { + return _database.IsUpdated(_metadata, flag); + } + + protected override bool IsFullDatabase() + { + return _database.IsFullDatabase(); + } + + protected override uint GetCount(SourceFlag flag) + { + return _database.GetCount(_metadata, flag); + } + + protected override ResultCode Get(SourceFlag flag, out int count, Span<CharInfoElement> elements) + { + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode Get1(SourceFlag flag, out int count, Span<CharInfo> elements) + { + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo) + { + newCharInfo = default; + + return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo); + } + + protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo) + { + if (age > Age.All || gender > Gender.All || race > Race.All) + { + charInfo = default; + + return ResultCode.InvalidArgument; + } + + _database.BuildRandom(age, gender, race, out charInfo); + + return ResultCode.Success; + } + + protected override ResultCode BuildDefault(uint index, out CharInfo charInfo) + { + if (index >= DefaultMii.TableLength) + { + charInfo = default; + + return ResultCode.InvalidArgument; + } + + _database.BuildDefault(index, out charInfo); + + return ResultCode.Success; + } + + protected override ResultCode Get2(SourceFlag flag, out int count, Span<StoreDataElement> elements) + { + if (!_isSystem) + { + count = -1; + + return ResultCode.PermissionDenied; + } + + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode Get3(SourceFlag flag, out int count, Span<StoreData> elements) + { + if (!_isSystem) + { + count = -1; + + return ResultCode.PermissionDenied; + } + + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData) + { + newStoreData = default; + + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData); + } + + protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index) + { + if (!_isSystem) + { + index = -1; + + return ResultCode.PermissionDenied; + } + + index = _database.FindIndex(createId, isSpecial); + + return ResultCode.Success; + } + + protected override ResultCode Move(CreateId createId, int newIndex) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + if (newIndex > 0 && _database.GetCount(_metadata, SourceFlag.Database) > newIndex) + { + return _database.Move(_metadata, newIndex, createId); + } + + return ResultCode.InvalidArgument; + } + + protected override ResultCode AddOrReplace(StoreData storeData) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.AddOrReplace(_metadata, storeData); + } + + protected override ResultCode Delete(CreateId createId) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.Delete(_metadata, createId); + } + + protected override ResultCode DestroyFile() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + return _database.DestroyFile(_metadata); + } + + protected override ResultCode DeleteFile() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + return _database.DeleteFile(); + } + + protected override ResultCode Format() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + _database.Format(_metadata); + + return ResultCode.Success; + } + + protected override ResultCode Import(ReadOnlySpan<byte> data) + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + throw new NotImplementedException(); + } + + protected override ResultCode Export(Span<byte> data) + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + throw new NotImplementedException(); + } + + protected override ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase) + { + if (!_isSystem) + { + isBrokenDatabase = false; + + return ResultCode.PermissionDenied; + } + + isBrokenDatabase = _database.IsBrokenDatabaseWithClearFlag(); + + return ResultCode.Success; + } + + protected override ResultCode GetIndex(CharInfo charInfo, out int index) + { + return _database.GetIndex(_metadata, charInfo, out index); + } + + protected override void SetInterfaceVersion(uint interfaceVersion) + { + _database.SetInterfaceVersion(_metadata, interfaceVersion); + } + + protected override ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo) + { + throw new NotImplementedException(); + } + + protected override ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo) + { + return _database.ConvertCoreDataToCharInfo(coreData, out charInfo); + } + + protected override ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData) + { + return _database.ConvertCharInfoToCoreData(charInfo, out coreData); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs new file mode 100644 index 00000000..e95364be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs @@ -0,0 +1,425 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.StaticService +{ + abstract class IDatabaseService : IpcService + { + [CommandCmif(0)] + // IsUpdated(SourceFlag flag) -> bool + public ResultCode IsUpdated(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + context.ResponseData.Write(IsUpdated(flag)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // IsFullDatabase() -> bool + public ResultCode IsFullDatabase(ServiceCtx context) + { + context.ResponseData.Write(IsFullDatabase()); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetCount(SourceFlag flag) -> u32 + public ResultCode GetCount(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + context.ResponseData.Write(GetCount(flag)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Get(SourceFlag flag) -> (s32 count, buffer<nn::mii::CharInfoRawElement, 6>) + public ResultCode Get(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span<CharInfoElement> elementsSpan = CreateSpanFromBuffer<CharInfoElement>(context, outputBuffer, true); + + ResultCode result = Get(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan.Slice(0, count); + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(4)] + // Get1(SourceFlag flag) -> (s32 count, buffer<nn::mii::CharInfo, 6>) + public ResultCode Get1(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span<CharInfo> elementsSpan = CreateSpanFromBuffer<CharInfo>(context, outputBuffer, true); + + ResultCode result = Get1(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan.Slice(0, count); + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(5)] + // UpdateLatest(nn::mii::CharInfo old_char_info, SourceFlag flag) -> nn::mii::CharInfo + public ResultCode UpdateLatest(ServiceCtx context) + { + CharInfo oldCharInfo = context.RequestData.ReadStruct<CharInfo>(); + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + ResultCode result = UpdateLatest(oldCharInfo, flag, out CharInfo newCharInfo); + + context.ResponseData.WriteStruct(newCharInfo); + + return result; + } + + [CommandCmif(6)] + // BuildRandom(Age age, Gender gender, Race race) -> nn::mii::CharInfo + public ResultCode BuildRandom(ServiceCtx context) + { + Age age = (Age)context.RequestData.ReadInt32(); + Gender gender = (Gender)context.RequestData.ReadInt32(); + Race race = (Race)context.RequestData.ReadInt32(); + + ResultCode result = BuildRandom(age, gender, race, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(7)] + // BuildDefault(u32 index) -> nn::mii::CharInfoRaw + public ResultCode BuildDefault(ServiceCtx context) + { + uint index = context.RequestData.ReadUInt32(); + + ResultCode result = BuildDefault(index, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(8)] + // Get2(SourceFlag flag) -> (u32 count, buffer<nn::mii::StoreDataElement, 6>) + public ResultCode Get2(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span<StoreDataElement> elementsSpan = CreateSpanFromBuffer<StoreDataElement>(context, outputBuffer, true); + + ResultCode result = Get2(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan.Slice(0, count); + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(9)] + // Get3(SourceFlag flag) -> (u32 count, buffer<nn::mii::StoreData, 6>) + public ResultCode Get3(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span<StoreData> elementsSpan = CreateSpanFromBuffer<StoreData>(context, outputBuffer, true); + + ResultCode result = Get3(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan.Slice(0, count); + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(10)] + // UpdateLatest1(nn::mii::StoreData old_store_data, SourceFlag flag) -> nn::mii::StoreData + public ResultCode UpdateLatest1(ServiceCtx context) + { + StoreData oldStoreData = context.RequestData.ReadStruct<StoreData>(); + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + ResultCode result = UpdateLatest1(oldStoreData, flag, out StoreData newStoreData); + + context.ResponseData.WriteStruct(newStoreData); + + return result; + } + + [CommandCmif(11)] + // FindIndex(nn::mii::CreateId create_id, bool is_special) -> s32 + public ResultCode FindIndex(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct<CreateId>(); + bool isSpecial = context.RequestData.ReadBoolean(); + + ResultCode result = FindIndex(createId, isSpecial, out int index); + + context.ResponseData.Write(index); + + return result; + } + + [CommandCmif(12)] + // Move(nn::mii::CreateId create_id, s32 new_index) + public ResultCode Move(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct<CreateId>(); + int newIndex = context.RequestData.ReadInt32(); + + return Move(createId, newIndex); + } + + [CommandCmif(13)] + // AddOrReplace(nn::mii::StoreData store_data) + public ResultCode AddOrReplace(ServiceCtx context) + { + StoreData storeData = context.RequestData.ReadStruct<StoreData>(); + + return AddOrReplace(storeData); + } + + [CommandCmif(14)] + // Delete(nn::mii::CreateId create_id) + public ResultCode Delete(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct<CreateId>(); + + return Delete(createId); + } + + [CommandCmif(15)] + // DestroyFile() + public ResultCode DestroyFile(ServiceCtx context) + { + return DestroyFile(); + } + + [CommandCmif(16)] + // DeleteFile() + public ResultCode DeleteFile(ServiceCtx context) + { + return DeleteFile(); + } + + [CommandCmif(17)] + // Format() + public ResultCode Format(ServiceCtx context) + { + return Format(); + } + + [CommandCmif(18)] + // Import(buffer<bytes, 5>) + public ResultCode Import(ServiceCtx context) + { + ReadOnlySpan<byte> data = CreateByteSpanFromBuffer(context, context.Request.SendBuff[0], false); + + return Import(data); + } + + [CommandCmif(19)] + // Export() -> buffer<bytes, 6> + public ResultCode Export(ServiceCtx context) + { + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span<byte> data = CreateByteSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Export(data); + + context.Memory.Write(outputBuffer.Position, data.ToArray()); + + return result; + } + + [CommandCmif(20)] + // IsBrokenDatabaseWithClearFlag() -> bool + public ResultCode IsBrokenDatabaseWithClearFlag(ServiceCtx context) + { + ResultCode result = IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase); + + context.ResponseData.Write(isBrokenDatabase); + + return result; + } + + [CommandCmif(21)] + // GetIndex(nn::mii::CharInfo char_info) -> s32 + public ResultCode GetIndex(ServiceCtx context) + { + CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>(); + + ResultCode result = GetIndex(charInfo, out int index); + + context.ResponseData.Write(index); + + return result; + } + + [CommandCmif(22)] // 5.0.0+ + // SetInterfaceVersion(u32 version) + public ResultCode SetInterfaceVersion(ServiceCtx context) + { + uint interfaceVersion = context.RequestData.ReadUInt32(); + + SetInterfaceVersion(interfaceVersion); + + return ResultCode.Success; + } + + [CommandCmif(23)] // 5.0.0+ + // Convert(nn::mii::Ver3StoreData ver3_store_data) -> nn::mii::CharInfo + public ResultCode Convert(ServiceCtx context) + { + Ver3StoreData ver3StoreData = context.RequestData.ReadStruct<Ver3StoreData>(); + + ResultCode result = Convert(ver3StoreData, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(24)] // 7.0.0+ + // ConvertCoreDataToCharInfo(nn::mii::CoreData core_data) -> nn::mii::CharInfo + public ResultCode ConvertCoreDataToCharInfo(ServiceCtx context) + { + CoreData coreData = context.RequestData.ReadStruct<CoreData>(); + + ResultCode result = ConvertCoreDataToCharInfo(coreData, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(25)] // 7.0.0+ + // ConvertCharInfoToCoreData(nn::mii::CharInfo char_info) -> nn::mii::CoreData + public ResultCode ConvertCharInfoToCoreData(ServiceCtx context) + { + CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>(); + + ResultCode result = ConvertCharInfoToCoreData(charInfo, out CoreData coreData); + + context.ResponseData.WriteStruct(coreData); + + return result; + } + + private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) + { + byte[] rawData; + + if (isOutput) + { + rawData = new byte[ipcBuff.Size]; + } + else + { + rawData = new byte[ipcBuff.Size]; + + context.Memory.Read(ipcBuff.Position, rawData); + } + + return new Span<byte>(rawData); + } + + private Span<T> CreateSpanFromBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) where T: unmanaged + { + return MemoryMarshal.Cast<byte, T>(CreateByteSpanFromBuffer(context, ipcBuff, isOutput)); + } + + private void WriteSpanToBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, Span<T> span) where T: unmanaged + { + Span<byte> rawData = MemoryMarshal.Cast<T, byte>(span); + + context.Memory.Write(ipcBuff.Position, rawData); + } + + protected abstract bool IsUpdated(SourceFlag flag); + + protected abstract bool IsFullDatabase(); + + protected abstract uint GetCount(SourceFlag flag); + + protected abstract ResultCode Get(SourceFlag flag, out int count, Span<CharInfoElement> elements); + + protected abstract ResultCode Get1(SourceFlag flag, out int count, Span<CharInfo> elements); + + protected abstract ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo); + + protected abstract ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo); + + protected abstract ResultCode BuildDefault(uint index, out CharInfo charInfo); + + protected abstract ResultCode Get2(SourceFlag flag, out int count, Span<StoreDataElement> elements); + + protected abstract ResultCode Get3(SourceFlag flag, out int count, Span<StoreData> elements); + + protected abstract ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData); + + protected abstract ResultCode FindIndex(CreateId createId, bool isSpecial, out int index); + + protected abstract ResultCode Move(CreateId createId, int newIndex); + + protected abstract ResultCode AddOrReplace(StoreData storeData); + + protected abstract ResultCode Delete(CreateId createId); + + protected abstract ResultCode DestroyFile(); + + protected abstract ResultCode DeleteFile(); + + protected abstract ResultCode Format(); + + protected abstract ResultCode Import(ReadOnlySpan<byte> data); + + protected abstract ResultCode Export(Span<byte> data); + + protected abstract ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase); + + protected abstract ResultCode GetIndex(CharInfo charInfo, out int index); + + protected abstract void SetInterfaceVersion(uint interfaceVersion); + + protected abstract ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo); + + protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo); + + protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs new file mode 100644 index 00000000..7beb6ec0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Age : uint + { + Young, + Normal, + Old, + All + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs new file mode 100644 index 00000000..a028b9be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum BeardType : byte + { + None, + Goatee, + GoateeLong, + LionsManeLong, + LionsMane, + Full, + + Min = 0, + Max = 5 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs new file mode 100644 index 00000000..256ec9e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs @@ -0,0 +1,329 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x58)] + struct CharInfo : IStoredData<CharInfo> + { + public CreateId CreateId; + public Nickname Nickname; + public FontRegion FontRegion; + public byte FavoriteColor; + public Gender Gender; + public byte Height; + public byte Build; + public byte Type; + public byte RegionMove; + public FacelineType FacelineType; + public FacelineColor FacelineColor; + public FacelineWrinkle FacelineWrinkle; + public FacelineMake FacelineMake; + public HairType HairType; + public CommonColor HairColor; + public HairFlip HairFlip; + public EyeType EyeType; + public CommonColor EyeColor; + public byte EyeScale; + public byte EyeAspect; + public byte EyeRotate; + public byte EyeX; + public byte EyeY; + public EyebrowType EyebrowType; + public CommonColor EyebrowColor; + public byte EyebrowScale; + public byte EyebrowAspect; + public byte EyebrowRotate; + public byte EyebrowX; + public byte EyebrowY; + public NoseType NoseType; + public byte NoseScale; + public byte NoseY; + public MouthType MouthType; + public CommonColor MouthColor; + public byte MouthScale; + public byte MouthAspect; + public byte MouthY; + public CommonColor BeardColor; + public BeardType BeardType; + public MustacheType MustacheType; + public byte MustacheScale; + public byte MustacheY; + public GlassType GlassType; + public CommonColor GlassColor; + public byte GlassScale; + public byte GlassY; + public MoleType MoleType; + public byte MoleScale; + public byte MoleX; + public byte MoleY; + public byte Reserved; + + byte IStoredData<CharInfo>.Type => Type; + + CreateId IStoredData<CharInfo>.CreateId => CreateId; + + public ResultCode InvalidData => ResultCode.InvalidCharInfo; + + public bool IsValid() + { + return Verify() == 0; + } + + public uint Verify() + { + if (!CreateId.IsValid) return 50; + if (!Nickname.IsValid()) return 51; + if ((byte)FontRegion > 3) return 23; + if (FavoriteColor > 11) return 22; + if (Gender > Gender.Max) return 24; + if ((sbyte)Height < 0) return 32; + if ((sbyte)Build < 0) return 3; + if (Type > 1) return 53; + if (RegionMove > 3) return 49; + if (FacelineType > FacelineType.Max) return 21; + if (FacelineColor > FacelineColor.Max) return 18; + if (FacelineWrinkle > FacelineWrinkle.Max) return 20; + if (FacelineMake > FacelineMake.Max) return 19; + if (HairType > HairType.Max) return 31; + if (HairColor > CommonColor.Max) return 29; + if (HairFlip > HairFlip.Max) return 30; + if (EyeType > EyeType.Max) return 8; + if (EyeColor > CommonColor.Max) return 5; + if (EyeScale > 7) return 7; + if (EyeAspect > 6) return 4; + if (EyeRotate > 7) return 6; + if (EyeX > 12) return 9; + if (EyeY > 18) return 10; + if (EyebrowType > EyebrowType.Max) return 15; + if (EyebrowColor > CommonColor.Max) return 12; + if (EyebrowScale > 8) return 14; + if (EyebrowAspect > 6) return 11; + if (EyebrowRotate > 11) return 13; + if (EyebrowX > 12) return 16; + if (EyebrowY - 3 > 15) return 17; + if (NoseType > NoseType.Max) return 47; + if (NoseScale > 8) return 46; + if (NoseY> 18) return 48; + if (MouthType > MouthType.Max) return 40; + if (MouthColor > CommonColor.Max) return 38; + if (MouthScale > 8) return 39; + if (MouthAspect > 6) return 37; + if (MouthY > 18) return 41; + if (BeardColor > CommonColor.Max) return 1; + if (BeardType > BeardType.Max) return 2; + if (MustacheType > MustacheType.Max) return 43; + if (MustacheScale > 8) return 42; + if (MustacheY > 16) return 44; + if (GlassType > GlassType.Max) return 27; + if (GlassColor > CommonColor.Max) return 25; + if (GlassScale > 7) return 26; + if (GlassY > 20) return 28; + if (MoleType > MoleType.Max) return 34; + if (MoleScale > 8) return 33; + if (MoleX > 16) return 35; + if (MoleY >= 31) return 36; + + return 0; + } + + public void SetFromStoreData(StoreData storeData) + { + Nickname = storeData.CoreData.Nickname; + CreateId = storeData.CreateId; + FontRegion = storeData.CoreData.FontRegion; + FavoriteColor = storeData.CoreData.FavoriteColor; + Gender = storeData.CoreData.Gender; + Height = storeData.CoreData.Height; + Build = storeData.CoreData.Build; + Type = storeData.CoreData.Type; + RegionMove = storeData.CoreData.RegionMove; + FacelineType = storeData.CoreData.FacelineType; + FacelineColor = storeData.CoreData.FacelineColor; + FacelineWrinkle = storeData.CoreData.FacelineWrinkle; + FacelineMake = storeData.CoreData.FacelineMake; + HairType = storeData.CoreData.HairType; + HairColor = storeData.CoreData.HairColor; + HairFlip = storeData.CoreData.HairFlip; + EyeType = storeData.CoreData.EyeType; + EyeColor = storeData.CoreData.EyeColor; + EyeScale = storeData.CoreData.EyeScale; + EyeAspect = storeData.CoreData.EyeAspect; + EyeRotate = storeData.CoreData.EyeRotate; + EyeX = storeData.CoreData.EyeX; + EyeY = storeData.CoreData.EyeY; + EyebrowType = storeData.CoreData.EyebrowType; + EyebrowColor = storeData.CoreData.EyebrowColor; + EyebrowScale = storeData.CoreData.EyebrowScale; + EyebrowAspect = storeData.CoreData.EyebrowAspect; + EyebrowRotate = storeData.CoreData.EyebrowRotate; + EyebrowX = storeData.CoreData.EyebrowX; + EyebrowY = storeData.CoreData.EyebrowY; + NoseType = storeData.CoreData.NoseType; + NoseScale = storeData.CoreData.NoseScale; + NoseY = storeData.CoreData.NoseY; + MouthType = storeData.CoreData.MouthType; + MouthColor = storeData.CoreData.MouthColor; + MouthScale = storeData.CoreData.MouthScale; + MouthAspect = storeData.CoreData.MouthAspect; + MouthY = storeData.CoreData.MouthY; + BeardColor = storeData.CoreData.BeardColor; + BeardType = storeData.CoreData.BeardType; + MustacheType = storeData.CoreData.MustacheType; + MustacheScale = storeData.CoreData.MustacheScale; + MustacheY = storeData.CoreData.MustacheY; + GlassType = storeData.CoreData.GlassType; + GlassColor = storeData.CoreData.GlassColor; + GlassScale = storeData.CoreData.GlassScale; + GlassY = storeData.CoreData.GlassY; + MoleType = storeData.CoreData.MoleType; + MoleScale = storeData.CoreData.MoleScale; + MoleX = storeData.CoreData.MoleX; + MoleY = storeData.CoreData.MoleY; + Reserved = 0; + } + + public void SetSource(Source source) + { + // Only implemented for Element variants. + } + + public static bool operator ==(CharInfo x, CharInfo y) + { + return x.Equals(y); + } + + public static bool operator !=(CharInfo x, CharInfo y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is CharInfo charInfo && Equals(charInfo); + } + + public bool Equals(CharInfo cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= Nickname == cmpObj.Nickname; + result &= CreateId == cmpObj.CreateId; + result &= FontRegion == cmpObj.FontRegion; + result &= FavoriteColor == cmpObj.FavoriteColor; + result &= Gender == cmpObj.Gender; + result &= Height == cmpObj.Height; + result &= Build == cmpObj.Build; + result &= Type == cmpObj.Type; + result &= RegionMove == cmpObj.RegionMove; + result &= FacelineType == cmpObj.FacelineType; + result &= FacelineColor == cmpObj.FacelineColor; + result &= FacelineWrinkle == cmpObj.FacelineWrinkle; + result &= FacelineMake == cmpObj.FacelineMake; + result &= HairType == cmpObj.HairType; + result &= HairColor == cmpObj.HairColor; + result &= HairFlip == cmpObj.HairFlip; + result &= EyeType == cmpObj.EyeType; + result &= EyeColor == cmpObj.EyeColor; + result &= EyeScale == cmpObj.EyeScale; + result &= EyeAspect == cmpObj.EyeAspect; + result &= EyeRotate == cmpObj.EyeRotate; + result &= EyeX == cmpObj.EyeX; + result &= EyeY == cmpObj.EyeY; + result &= EyebrowType == cmpObj.EyebrowType; + result &= EyebrowColor == cmpObj.EyebrowColor; + result &= EyebrowScale == cmpObj.EyebrowScale; + result &= EyebrowAspect == cmpObj.EyebrowAspect; + result &= EyebrowRotate == cmpObj.EyebrowRotate; + result &= EyebrowX == cmpObj.EyebrowX; + result &= EyebrowY == cmpObj.EyebrowY; + result &= NoseType == cmpObj.NoseType; + result &= NoseScale == cmpObj.NoseScale; + result &= NoseY == cmpObj.NoseY; + result &= MouthType == cmpObj.MouthType; + result &= MouthColor == cmpObj.MouthColor; + result &= MouthScale == cmpObj.MouthScale; + result &= MouthAspect == cmpObj.MouthAspect; + result &= MouthY == cmpObj.MouthY; + result &= BeardColor == cmpObj.BeardColor; + result &= BeardType == cmpObj.BeardType; + result &= MustacheType == cmpObj.MustacheType; + result &= MustacheScale == cmpObj.MustacheScale; + result &= MustacheY == cmpObj.MustacheY; + result &= GlassType == cmpObj.GlassType; + result &= GlassColor == cmpObj.GlassColor; + result &= GlassScale == cmpObj.GlassScale; + result &= GlassY == cmpObj.GlassY; + result &= MoleType == cmpObj.MoleType; + result &= MoleScale == cmpObj.MoleScale; + result &= MoleX == cmpObj.MoleX; + result &= MoleY == cmpObj.MoleY; + + return result; + } + + public override int GetHashCode() + { + HashCode hashCode = new HashCode(); + + hashCode.Add(Nickname); + hashCode.Add(CreateId); + hashCode.Add(FontRegion); + hashCode.Add(FavoriteColor); + hashCode.Add(Gender); + hashCode.Add(Height); + hashCode.Add(Build); + hashCode.Add(Type); + hashCode.Add(RegionMove); + hashCode.Add(FacelineType); + hashCode.Add(FacelineColor); + hashCode.Add(FacelineWrinkle); + hashCode.Add(FacelineMake); + hashCode.Add(HairType); + hashCode.Add(HairColor); + hashCode.Add(HairFlip); + hashCode.Add(EyeType); + hashCode.Add(EyeColor); + hashCode.Add(EyeScale); + hashCode.Add(EyeAspect); + hashCode.Add(EyeRotate); + hashCode.Add(EyeX); + hashCode.Add(EyeY); + hashCode.Add(EyebrowType); + hashCode.Add(EyebrowColor); + hashCode.Add(EyebrowScale); + hashCode.Add(EyebrowAspect); + hashCode.Add(EyebrowRotate); + hashCode.Add(EyebrowX); + hashCode.Add(EyebrowY); + hashCode.Add(NoseType); + hashCode.Add(NoseScale); + hashCode.Add(NoseY); + hashCode.Add(MouthType); + hashCode.Add(MouthColor); + hashCode.Add(MouthScale); + hashCode.Add(MouthAspect); + hashCode.Add(MouthY); + hashCode.Add(BeardColor); + hashCode.Add(BeardType); + hashCode.Add(MustacheType); + hashCode.Add(MustacheScale); + hashCode.Add(MustacheY); + hashCode.Add(GlassType); + hashCode.Add(GlassColor); + hashCode.Add(GlassScale); + hashCode.Add(GlassY); + hashCode.Add(MoleType); + hashCode.Add(MoleScale); + hashCode.Add(MoleX); + hashCode.Add(MoleY); + + return hashCode.ToHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs new file mode 100644 index 00000000..f1f850fd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5C)] + struct CharInfoElement : IElement + { + public CharInfo CharInfo; + public Source Source; + + public void SetFromStoreData(StoreData storeData) + { + CharInfo.SetFromStoreData(storeData); + } + + public void SetSource(Source source) + { + Source = source; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs new file mode 100644 index 00000000..8b613850 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs @@ -0,0 +1,9 @@ + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum CommonColor : byte + { + Min = 0, + Max = 99 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs new file mode 100644 index 00000000..f3a101d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs @@ -0,0 +1,911 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.HLE.HOS.Services.Mii.Types.RandomMiiConstants; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct CoreData : IEquatable<CoreData> + { + public const int Size = 0x30; + + private byte _storage; + + public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size); + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x18)] + public struct ElementInfo + { + public int ByteOffset; + public int BitOffset; + public int BitWidth; + public int MinValue; + public int MaxValue; + public int Unknown; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetValue(ElementInfoIndex index) + { + ElementInfo info = ElementInfos[(int)index]; + + return ((Storage[info.ByteOffset] >> info.BitOffset) & ~(-1 << info.BitWidth)) + info.MinValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetValue(ElementInfoIndex index, int value) + { + ElementInfo info = ElementInfos[(int)index]; + + int newValue = Storage[info.ByteOffset] & ~(~(-1 << info.BitWidth) << info.BitOffset) | (((value - info.MinValue) & ~(-1 << info.BitWidth)) << info.BitOffset); + + Storage[info.ByteOffset] = (byte)newValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsElementValid(ElementInfoIndex index) + { + ElementInfo info = ElementInfos[(int)index]; + + int value = GetValue(index); + + return value >= info.MinValue && value <= info.MaxValue; + } + + public bool IsValid(bool acceptEmptyNickname = false) + { + if (!Nickname.IsValid() || (!acceptEmptyNickname && Nickname.IsEmpty())) + { + return false; + } + + for (int i = 0; i < ElementInfos.Length; i++) + { + if (!IsElementValid((ElementInfoIndex)i)) + { + return false; + } + } + + return true; + } + + public void SetDefault() + { + Storage.Fill(0); + + Nickname = Nickname.Default; + } + + public HairType HairType + { + get => (HairType)GetValue(ElementInfoIndex.HairType); + set => SetValue(ElementInfoIndex.HairType, (int)value); + } + + public byte Height + { + get => (byte)GetValue(ElementInfoIndex.Height); + set => SetValue(ElementInfoIndex.Height, value); + } + + public MoleType MoleType + { + get => (MoleType)GetValue(ElementInfoIndex.MoleType); + set => SetValue(ElementInfoIndex.MoleType, (byte)value); + } + + public byte Build + { + get => (byte)GetValue(ElementInfoIndex.Build); + set => SetValue(ElementInfoIndex.Build, value); + } + + public HairFlip HairFlip + { + get => (HairFlip)GetValue(ElementInfoIndex.HairFlip); + set => SetValue(ElementInfoIndex.HairFlip, (byte)value); + } + + public CommonColor HairColor + { + get => (CommonColor)GetValue(ElementInfoIndex.HairColor); + set => SetValue(ElementInfoIndex.HairColor, (int)value); + } + + public byte Type + { + get => (byte)GetValue(ElementInfoIndex.Type); + set => SetValue(ElementInfoIndex.Type, value); + } + + public CommonColor EyeColor + { + get => (CommonColor)GetValue(ElementInfoIndex.EyeColor); + set => SetValue(ElementInfoIndex.EyeColor, (int)value); + } + + public Gender Gender + { + get => (Gender)GetValue(ElementInfoIndex.Gender); + set => SetValue(ElementInfoIndex.Gender, (int)value); + } + + public CommonColor EyebrowColor + { + get => (CommonColor)GetValue(ElementInfoIndex.EyebrowColor); + set => SetValue(ElementInfoIndex.EyebrowColor, (int)value); + } + + public CommonColor MouthColor + { + get => (CommonColor)GetValue(ElementInfoIndex.MouthColor); + set => SetValue(ElementInfoIndex.MouthColor, (int)value); + } + + public CommonColor BeardColor + { + get => (CommonColor)GetValue(ElementInfoIndex.BeardColor); + set => SetValue(ElementInfoIndex.BeardColor, (byte)value); + } + + public CommonColor GlassColor + { + get => (CommonColor)GetValue(ElementInfoIndex.GlassColor); + set => SetValue(ElementInfoIndex.GlassColor, (int)value); + } + + public EyeType EyeType + { + get => (EyeType)GetValue(ElementInfoIndex.EyeType); + set => SetValue(ElementInfoIndex.EyeType, (int)value); + } + + public byte RegionMove + { + get => (byte)GetValue(ElementInfoIndex.RegionMove); + set => SetValue(ElementInfoIndex.RegionMove, value); + } + + public MouthType MouthType + { + get => (MouthType)GetValue(ElementInfoIndex.MouthType); + set => SetValue(ElementInfoIndex.MouthType, (int)value); + } + + public FontRegion FontRegion + { + get => (FontRegion)GetValue(ElementInfoIndex.FontRegion); + set => SetValue(ElementInfoIndex.FontRegion, (byte)value); + } + + public byte EyeY + { + get => (byte)GetValue(ElementInfoIndex.EyeY); + set => SetValue(ElementInfoIndex.EyeY, value); + } + + public byte GlassScale + { + get => (byte)GetValue(ElementInfoIndex.GlassScale); + set => SetValue(ElementInfoIndex.GlassScale, value); + } + + public EyebrowType EyebrowType + { + get => (EyebrowType)GetValue(ElementInfoIndex.EyebrowType); + set => SetValue(ElementInfoIndex.EyebrowType, (int)value); + } + + public MustacheType MustacheType + { + get => (MustacheType)GetValue(ElementInfoIndex.MustacheType); + set => SetValue(ElementInfoIndex.MustacheType, (int)value); + } + + public NoseType NoseType + { + get => (NoseType)GetValue(ElementInfoIndex.NoseType); + set => SetValue(ElementInfoIndex.NoseType, (int)value); + } + + public BeardType BeardType + { + get => (BeardType)GetValue(ElementInfoIndex.BeardType); + set => SetValue(ElementInfoIndex.BeardType, (int)value); + } + + public byte NoseY + { + get => (byte)GetValue(ElementInfoIndex.NoseY); + set => SetValue(ElementInfoIndex.NoseY, value); + } + + public byte MouthAspect + { + get => (byte)GetValue(ElementInfoIndex.MouthAspect); + set => SetValue(ElementInfoIndex.MouthAspect, value); + } + + public byte MouthY + { + get => (byte)GetValue(ElementInfoIndex.MouthY); + set => SetValue(ElementInfoIndex.MouthY, value); + } + + public byte EyebrowAspect + { + get => (byte)GetValue(ElementInfoIndex.EyebrowAspect); + set => SetValue(ElementInfoIndex.EyebrowAspect, value); + } + + public byte MustacheY + { + get => (byte)GetValue(ElementInfoIndex.MustacheY); + set => SetValue(ElementInfoIndex.MustacheY, value); + } + + public byte EyeRotate + { + get => (byte)GetValue(ElementInfoIndex.EyeRotate); + set => SetValue(ElementInfoIndex.EyeRotate, value); + } + + public byte GlassY + { + get => (byte)GetValue(ElementInfoIndex.GlassY); + set => SetValue(ElementInfoIndex.GlassY, value); + } + + public byte EyeAspect + { + get => (byte)GetValue(ElementInfoIndex.EyeAspect); + set => SetValue(ElementInfoIndex.EyeAspect, value); + } + + public byte MoleX + { + get => (byte)GetValue(ElementInfoIndex.MoleX); + set => SetValue(ElementInfoIndex.MoleX, value); + } + + public byte EyeScale + { + get => (byte)GetValue(ElementInfoIndex.EyeScale); + set => SetValue(ElementInfoIndex.EyeScale, value); + } + + public byte MoleY + { + get => (byte)GetValue(ElementInfoIndex.MoleY); + set => SetValue(ElementInfoIndex.MoleY, value); + } + + public GlassType GlassType + { + get => (GlassType)GetValue(ElementInfoIndex.GlassType); + set => SetValue(ElementInfoIndex.GlassType, (int)value); + } + + public byte FavoriteColor + { + get => (byte)GetValue(ElementInfoIndex.FavoriteColor); + set => SetValue(ElementInfoIndex.FavoriteColor, value); + } + + public FacelineType FacelineType + { + get => (FacelineType)GetValue(ElementInfoIndex.FacelineType); + set => SetValue(ElementInfoIndex.FacelineType, (int)value); + } + + public FacelineColor FacelineColor + { + get => (FacelineColor)GetValue(ElementInfoIndex.FacelineColor); + set => SetValue(ElementInfoIndex.FacelineColor, (int)value); + } + + public FacelineWrinkle FacelineWrinkle + { + get => (FacelineWrinkle)GetValue(ElementInfoIndex.FacelineWrinkle); + set => SetValue(ElementInfoIndex.FacelineWrinkle, (int)value); + } + + public FacelineMake FacelineMake + { + get => (FacelineMake)GetValue(ElementInfoIndex.FacelineMake); + set => SetValue(ElementInfoIndex.FacelineMake, (int)value); + } + + public byte EyeX + { + get => (byte)GetValue(ElementInfoIndex.EyeX); + set => SetValue(ElementInfoIndex.EyeX, value); + } + + public byte EyebrowScale + { + get => (byte)GetValue(ElementInfoIndex.EyebrowScale); + set => SetValue(ElementInfoIndex.EyebrowScale, value); + } + + public byte EyebrowRotate + { + get => (byte)GetValue(ElementInfoIndex.EyebrowRotate); + set => SetValue(ElementInfoIndex.EyebrowRotate, value); + } + + public byte EyebrowX + { + get => (byte)GetValue(ElementInfoIndex.EyebrowX); + set => SetValue(ElementInfoIndex.EyebrowX, value); + } + + public byte EyebrowY + { + get => (byte)GetValue(ElementInfoIndex.EyebrowY); + set => SetValue(ElementInfoIndex.EyebrowY, value); + } + + public byte NoseScale + { + get => (byte)GetValue(ElementInfoIndex.NoseScale); + set => SetValue(ElementInfoIndex.NoseScale, value); + } + + public byte MouthScale + { + get => (byte)GetValue(ElementInfoIndex.MouthScale); + set => SetValue(ElementInfoIndex.MouthScale, value); + } + + public byte MustacheScale + { + get => (byte)GetValue(ElementInfoIndex.MustacheScale); + set => SetValue(ElementInfoIndex.MustacheScale, value); + } + + public byte MoleScale + { + get => (byte)GetValue(ElementInfoIndex.MoleScale); + set => SetValue(ElementInfoIndex.MoleScale, value); + } + + public Span<byte> GetNicknameStorage() + { + return Storage.Slice(0x1c); + } + + public Nickname Nickname + { + get => Nickname.FromBytes(GetNicknameStorage()); + set => value.Raw.Slice(0, 20).CopyTo(GetNicknameStorage()); + } + + public static CoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race) + { + CoreData coreData = new CoreData(); + + coreData.SetDefault(); + + if (gender == Gender.All) + { + gender = (Gender)utilImpl.GetRandom((int)gender); + } + + if (age == Age.All) + { + int ageDecade = utilImpl.GetRandom(10); + + if (ageDecade >= 8) + { + age = Age.Old; + } + else if (ageDecade >= 4) + { + age = Age.Normal; + } + else + { + age = Age.Young; + } + } + + if (race == Race.All) + { + int raceTempValue = utilImpl.GetRandom(10); + + if (raceTempValue >= 8) + { + race = Race.Black; + } + else if (raceTempValue >= 4) + { + race = Race.White; + } + else + { + race = Race.Asian; + } + } + + int axisY = 0; + + if (gender == Gender.Female && age == Age.Young) + { + axisY = utilImpl.GetRandom(3); + } + + int indexFor4 = 3 * (int)age + 9 * (int)gender + (int)race; + + var facelineTypeInfo = RandomMiiFacelineArray[indexFor4]; + var facelineColorInfo = RandomMiiFacelineColorArray[3 * (int)gender + (int)race]; + var facelineWrinkleInfo = RandomMiiFacelineWrinkleArray[indexFor4]; + var facelineMakeInfo = RandomMiiFacelineMakeArray[indexFor4]; + var hairTypeInfo = RandomMiiHairTypeArray[indexFor4]; + var hairColorInfo = RandomMiiHairColorArray[3 * (int)race + (int)age]; + var eyeTypeInfo = RandomMiiEyeTypeArray[indexFor4]; + var eyeColorInfo = RandomMiiEyeColorArray[(int)race]; + var eyebrowTypeInfo = RandomMiiEyebrowTypeArray[indexFor4]; + var noseTypeInfo = RandomMiiNoseTypeArray[indexFor4]; + var mouthTypeInfo = RandomMiiMouthTypeArray[indexFor4]; + var glassTypeInfo = RandomMiiGlassTypeArray[(int)age]; + + // Faceline + coreData.FacelineType = (FacelineType)facelineTypeInfo.Values[utilImpl.GetRandom(facelineTypeInfo.ValuesCount)]; + coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[facelineColorInfo.Values[utilImpl.GetRandom(facelineColorInfo.ValuesCount)]]; + coreData.FacelineWrinkle = (FacelineWrinkle)facelineWrinkleInfo.Values[utilImpl.GetRandom(facelineWrinkleInfo.ValuesCount)]; + coreData.FacelineMake = (FacelineMake)facelineMakeInfo.Values[utilImpl.GetRandom(facelineMakeInfo.ValuesCount)]; + + // Hair + coreData.HairType = (HairType)hairTypeInfo.Values[utilImpl.GetRandom(hairTypeInfo.ValuesCount)]; + coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[hairColorInfo.Values[utilImpl.GetRandom(hairColorInfo.ValuesCount)]]; + coreData.HairFlip = (HairFlip)utilImpl.GetRandom((int)HairFlip.Max + 1); + + // Eye + coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)]; + + int eyeRotateKey1 = gender != Gender.Male ? 4 : 2; + int eyeRotateKey2 = gender != Gender.Male ? 3 : 4; + + byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2); + byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]); + + coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[eyeColorInfo.Values[utilImpl.GetRandom(eyeColorInfo.ValuesCount)]]; + coreData.EyeScale = 4; + coreData.EyeAspect = 3; + coreData.EyeRotate = (byte)(eyeRotateOffset - eyeRotate); + coreData.EyeX = 2; + coreData.EyeY = (byte)(axisY + 12); + + // Eyebrow + coreData.EyebrowType = (EyebrowType)eyebrowTypeInfo.Values[utilImpl.GetRandom(eyebrowTypeInfo.ValuesCount)]; + + int eyebrowRotateKey = race == Race.Asian ? 6 : 0; + int eyebrowY = race == Race.Asian ? 9 : 10; + + byte eyebrowRotateOffset = (byte)(32 - EyebrowRotateTable[eyebrowRotateKey] + 6); + byte eyebrowRotate = (byte)(32 - EyebrowRotateTable[(int)coreData.EyebrowType]); + + coreData.EyebrowColor = coreData.HairColor; + coreData.EyebrowScale = 4; + coreData.EyebrowAspect = 3; + coreData.EyebrowRotate = (byte)(eyebrowRotateOffset - eyebrowRotate); + coreData.EyebrowX = 2; + coreData.EyebrowY = (byte)(axisY + eyebrowY); + + // Nose + int noseScale = gender == Gender.Female ? 3 : 4; + + coreData.NoseType = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)]; + coreData.NoseScale = (byte)noseScale; + coreData.NoseY = (byte)(axisY + 9); + + // Mouth + int mouthColor = gender == Gender.Female ? utilImpl.GetRandom(0, 4) : 0; + + coreData.MouthType = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)]; + coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor]; + coreData.MouthScale = 4; + coreData.MouthAspect = 3; + coreData.MouthY = (byte)(axisY + 13); + + // Beard & Mustache + coreData.BeardColor = coreData.HairColor; + coreData.MustacheScale = 4; + + if (gender == Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2) + { + BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3); + + BeardType beardType = BeardType.None; + MustacheType mustacheType = MustacheType.None; + + if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Beard) == BeardAndMustacheFlag.Beard) + { + beardType = (BeardType)utilImpl.GetRandom((int)BeardType.Goatee, (int)BeardType.Full); + } + + if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Mustache) == BeardAndMustacheFlag.Mustache) + { + mustacheType = (MustacheType)utilImpl.GetRandom((int)MustacheType.Walrus, (int)MustacheType.Toothbrush); + } + + coreData.MustacheType = mustacheType; + coreData.BeardType = beardType; + coreData.MustacheY = 10; + } + else + { + coreData.MustacheType = MustacheType.None; + coreData.BeardType = BeardType.None; + coreData.MustacheY = (byte)(axisY + 10); + } + + // Glass + int glassTypeStart = utilImpl.GetRandom(100); + GlassType glassType = GlassType.None; + + while (glassTypeStart < glassTypeInfo.Values[(int)glassType]) + { + glassType++; + + if ((int)glassType >= glassTypeInfo.ValuesCount) + { + throw new InvalidOperationException("glassTypeStart shouldn't exceed glassTypeInfo.ValuesCount"); + } + } + + coreData.GlassType = glassType; + coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[0]; + coreData.GlassScale = 4; + coreData.GlassY = (byte)(axisY + 10); + + // Mole + coreData.MoleType = 0; + coreData.MoleScale = 4; + coreData.MoleX = 2; + coreData.MoleY = 20; + + // Body sizing + coreData.Height = 64; + coreData.Build = 64; + + // Misc + coreData.Nickname = Nickname.Default; + coreData.Gender = gender; + coreData.FavoriteColor = (byte)utilImpl.GetRandom(0, 11); + coreData.RegionMove = 0; + coreData.FontRegion = 0; + coreData.Type = 0; + + return coreData; + } + + public void SetFromCharInfo(CharInfo charInfo) + { + Nickname = charInfo.Nickname; + FontRegion = charInfo.FontRegion; + FavoriteColor = charInfo.FavoriteColor; + Gender = charInfo.Gender; + Height = charInfo.Height; + Build = charInfo.Build; + Type = charInfo.Type; + RegionMove = charInfo.RegionMove; + FacelineType = charInfo.FacelineType; + FacelineColor = charInfo.FacelineColor; + FacelineWrinkle = charInfo.FacelineWrinkle; + FacelineMake = charInfo.FacelineMake; + HairType = charInfo.HairType; + HairColor = charInfo.HairColor; + HairFlip = charInfo.HairFlip; + EyeType = charInfo.EyeType; + EyeColor = charInfo.EyeColor; + EyeScale = charInfo.EyeScale; + EyeAspect = charInfo.EyeAspect; + EyeRotate = charInfo.EyeRotate; + EyeX = charInfo.EyeX; + EyeY = charInfo.EyeY; + EyebrowType = charInfo.EyebrowType; + EyebrowColor = charInfo.EyebrowColor; + EyebrowScale = charInfo.EyebrowScale; + EyebrowAspect = charInfo.EyebrowAspect; + EyebrowRotate = charInfo.EyebrowRotate; + EyebrowX = charInfo.EyebrowX; + EyebrowY = charInfo.EyebrowY; + NoseType = charInfo.NoseType; + NoseScale = charInfo.NoseScale; + NoseY = charInfo.NoseY; + MouthType = charInfo.MouthType; + MouthColor = charInfo.MouthColor; + MouthScale = charInfo.MouthScale; + MouthAspect = charInfo.MouthAspect; + MouthY = charInfo.MouthY; + BeardColor = charInfo.BeardColor; + BeardType = charInfo.BeardType; + MustacheType = charInfo.MustacheType; + MustacheScale = charInfo.MustacheScale; + MustacheY = charInfo.MustacheY; + GlassType = charInfo.GlassType; + GlassColor = charInfo.GlassColor; + GlassScale = charInfo.GlassScale; + GlassY = charInfo.GlassY; + MoleType = charInfo.MoleType; + MoleScale = charInfo.MoleScale; + MoleX = charInfo.MoleX; + MoleY = charInfo.MoleY; + } + + public static bool operator ==(CoreData x, CoreData y) + { + return x.Equals(y); + } + + public static bool operator !=(CoreData x, CoreData y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is CoreData coreData && Equals(coreData); + } + + public bool Equals(CoreData cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= Nickname == cmpObj.Nickname; + result &= FontRegion == cmpObj.FontRegion; + result &= FavoriteColor == cmpObj.FavoriteColor; + result &= Gender == cmpObj.Gender; + result &= Height == cmpObj.Height; + result &= Build == cmpObj.Build; + result &= Type == cmpObj.Type; + result &= RegionMove == cmpObj.RegionMove; + result &= FacelineType == cmpObj.FacelineType; + result &= FacelineColor == cmpObj.FacelineColor; + result &= FacelineWrinkle == cmpObj.FacelineWrinkle; + result &= FacelineMake == cmpObj.FacelineMake; + result &= HairType == cmpObj.HairType; + result &= HairColor == cmpObj.HairColor; + result &= HairFlip == cmpObj.HairFlip; + result &= EyeType == cmpObj.EyeType; + result &= EyeColor == cmpObj.EyeColor; + result &= EyeScale == cmpObj.EyeScale; + result &= EyeAspect == cmpObj.EyeAspect; + result &= EyeRotate == cmpObj.EyeRotate; + result &= EyeX == cmpObj.EyeX; + result &= EyeY == cmpObj.EyeY; + result &= EyebrowType == cmpObj.EyebrowType; + result &= EyebrowColor == cmpObj.EyebrowColor; + result &= EyebrowScale == cmpObj.EyebrowScale; + result &= EyebrowAspect == cmpObj.EyebrowAspect; + result &= EyebrowRotate == cmpObj.EyebrowRotate; + result &= EyebrowX == cmpObj.EyebrowX; + result &= EyebrowY == cmpObj.EyebrowY; + result &= NoseType == cmpObj.NoseType; + result &= NoseScale == cmpObj.NoseScale; + result &= NoseY == cmpObj.NoseY; + result &= MouthType == cmpObj.MouthType; + result &= MouthColor == cmpObj.MouthColor; + result &= MouthScale == cmpObj.MouthScale; + result &= MouthAspect == cmpObj.MouthAspect; + result &= MouthY == cmpObj.MouthY; + result &= BeardColor == cmpObj.BeardColor; + result &= BeardType == cmpObj.BeardType; + result &= MustacheType == cmpObj.MustacheType; + result &= MustacheScale == cmpObj.MustacheScale; + result &= MustacheY == cmpObj.MustacheY; + result &= GlassType == cmpObj.GlassType; + result &= GlassColor == cmpObj.GlassColor; + result &= GlassScale == cmpObj.GlassScale; + result &= GlassY == cmpObj.GlassY; + result &= MoleType == cmpObj.MoleType; + result &= MoleScale == cmpObj.MoleScale; + result &= MoleX == cmpObj.MoleX; + result &= MoleY == cmpObj.MoleY; + + return result; + } + + public override int GetHashCode() + { + HashCode hashCode = new HashCode(); + + hashCode.Add(Nickname); + hashCode.Add(FontRegion); + hashCode.Add(FavoriteColor); + hashCode.Add(Gender); + hashCode.Add(Height); + hashCode.Add(Build); + hashCode.Add(Type); + hashCode.Add(RegionMove); + hashCode.Add(FacelineType); + hashCode.Add(FacelineColor); + hashCode.Add(FacelineWrinkle); + hashCode.Add(FacelineMake); + hashCode.Add(HairType); + hashCode.Add(HairColor); + hashCode.Add(HairFlip); + hashCode.Add(EyeType); + hashCode.Add(EyeColor); + hashCode.Add(EyeScale); + hashCode.Add(EyeAspect); + hashCode.Add(EyeRotate); + hashCode.Add(EyeX); + hashCode.Add(EyeY); + hashCode.Add(EyebrowType); + hashCode.Add(EyebrowColor); + hashCode.Add(EyebrowScale); + hashCode.Add(EyebrowAspect); + hashCode.Add(EyebrowRotate); + hashCode.Add(EyebrowX); + hashCode.Add(EyebrowY); + hashCode.Add(NoseType); + hashCode.Add(NoseScale); + hashCode.Add(NoseY); + hashCode.Add(MouthType); + hashCode.Add(MouthColor); + hashCode.Add(MouthScale); + hashCode.Add(MouthAspect); + hashCode.Add(MouthY); + hashCode.Add(BeardColor); + hashCode.Add(BeardType); + hashCode.Add(MustacheType); + hashCode.Add(MustacheScale); + hashCode.Add(MustacheY); + hashCode.Add(GlassType); + hashCode.Add(GlassColor); + hashCode.Add(GlassScale); + hashCode.Add(GlassY); + hashCode.Add(MoleType); + hashCode.Add(MoleScale); + hashCode.Add(MoleX); + hashCode.Add(MoleY); + + return hashCode.ToHashCode(); + } + + private static ReadOnlySpan<ElementInfo> ElementInfos => MemoryMarshal.Cast<byte, ElementInfo>(ElementInfoArray); + + private enum ElementInfoIndex : int + { + HairType, + Height, + MoleType, + Build, + HairFlip, + HairColor, + Type, + EyeColor, + Gender, + EyebrowColor, + MouthColor, + BeardColor, + GlassColor, + EyeType, + RegionMove, + MouthType, + FontRegion, + EyeY, + GlassScale, + EyebrowType, + MustacheType, + NoseType, + BeardType, + NoseY, + MouthAspect, + MouthY, + EyebrowAspect, + MustacheY, + EyeRotate, + GlassY, + EyeAspect, + MoleX, + EyeScale, + MoleY, + GlassType, + FavoriteColor, + FacelineType, + FacelineColor, + FacelineWrinkle, + FacelineMake, + EyeX, + EyebrowScale, + EyebrowRotate, + EyebrowX, + EyebrowY, + NoseScale, + MouthScale, + MustacheScale, + MoleScale + } + + #region "Element Info Array" + private static ReadOnlySpan<byte> ElementInfoArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x83, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs new file mode 100644 index 00000000..c1a97f52 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct CreateId : IEquatable<CreateId> + { + public UInt128 Raw; + + public bool IsNull => Raw == UInt128.Zero; + public bool IsValid => !IsNull && ((Raw >> 64) & 0xC0) == 0x80; + + public CreateId(UInt128 raw) + { + Raw = raw; + } + + public static bool operator ==(CreateId x, CreateId y) + { + return x.Equals(y); + } + + public static bool operator !=(CreateId x, CreateId y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is CreateId createId && Equals(createId); + } + + public bool Equals(CreateId cmpObj) + { + // Nintendo additionally check that the CreatorId is valid before doing the actual comparison. + return IsValid && Raw == cmpObj.Raw; + } + + public override int GetHashCode() + { + return Raw.GetHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs new file mode 100644 index 00000000..285a9242 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs @@ -0,0 +1,197 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct DefaultMii + { + public const int Size = 0xD8; + + public int FacelineType; + public int FacelineColorVer3; + public int FacelineWrinkle; + public int FacelineMake; + public int HairType; + public int HairColorVer3; + public int HairFlip; + public int EyeType; + public int EyeColorVer3; + public int EyeScale; + public int EyeAspect; + public int EyeRotate; + public int EyeX; + public int EyeY; + public int EyebrowType; + public int EyebrowColorVer3; + public int EyebrowScale; + public int EyebrowAspect; + public int EyebrowRotate; + public int EyebrowX; + public int EyebrowY; + public int NoseType; + public int NoseScale; + public int NoseY; + public int MouthType; + public int MouthColorVer3; + public int MouthScale; + public int MouthAspect; + public int MouthY; + public int MustacheType; + public int BeardType; + public int BeardColorVer3; + public int MustacheScale; + public int MustacheY; + public int GlassType; + public int GlassColorVer3; + public int GlassScale; + public int GlassY; + public int MoleType; + public int MoleScale; + public int MoleX; + public int MoleY; + public int Height; + public int Build; + public int Gender; + public int FavoriteColor; + public int RegionMove; + public int FontRegion; + public int Type; + + private byte _nicknameFirstByte; + + public Span<byte> NicknameStorage => MemoryMarshal.CreateSpan(ref _nicknameFirstByte, 20); + + public Nickname Nickname + { + get => Nickname.FromBytes(NicknameStorage); + set => value.Raw.Slice(0, 20).CopyTo(NicknameStorage); + } + + public static ReadOnlySpan<DefaultMii> Table => MemoryMarshal.Cast<byte, DefaultMii>(TableRawArray); + + // The first 2 Mii in the default table are used as base for Male/Female in editor but not exposed via IPC. + public static int TableLength => _fromIndex.Length; + + private static readonly int[] _fromIndex = new int[] { 2, 3, 4, 5, 6, 7 }; + + public static DefaultMii GetDefaultMii(uint index) + { + return Table[_fromIndex[index]]; + } + + #region "Raw Table Array" + private static ReadOnlySpan<byte> TableRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs new file mode 100644 index 00000000..2e4502ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs @@ -0,0 +1,69 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum EyeType : byte + { + Normal, + NormalLash, + WhiteLash, + WhiteNoBottom, + OvalAngledWhite, + AngryWhite, + DotLashType1, + Line, + DotLine, + OvalWhite, + RoundedWhite, + NormalShadow, + CircleWhite, + Circle, + CircleWhiteStroke, + NormalOvalNoBottom, + NormalOvalLarge, + NormalRoundedNoBottom, + SmallLash, + Small, + TwoSmall, + NormalLongLash, + WhiteTwoLashes, + WhiteThreeLashes, + DotAngry, + DotAngled, + Oval, + SmallWhite, + WhiteAngledNoBottom, + WhiteAngledNoLeft, + SmallWhiteTwoLashes, + LeafWhiteLash, + WhiteLargeNoBottom, + Dot, + DotLashType2, + DotThreeLashes, + WhiteOvalTop, + WhiteOvalBottom, + WhiteOvalBottomFlat, + WhiteOvalTwoLashes, + WhiteOvalThreeLashes, + WhiteOvalNoBottomTwoLashes, + DotWhite, + WhiteOvalTopFlat, + WhiteThinLeaf, + StarThreeLashes, + LineTwoLashes, + CrowsFeet, + WhiteNoBottomFlat, + WhiteNoBottomRounded, + WhiteSmallBottomLine, + WhiteNoBottomLash, + WhiteNoPartialBottomLash, + WhiteOvalBottomLine, + WhiteNoBottomLashTopLine, + WhiteNoPartialBottomTwoLashes, + NormalTopLine, + WhiteOvalLash, + RoundTired, + WhiteLarge, + + Min = 0, + Max = 59 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs new file mode 100644 index 00000000..af870e10 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum EyebrowType : byte + { + FlatAngledLarge, + LowArchRoundedThin, + SoftAngledLarge, + MediumArchRoundedThin, + RoundedMedium, + LowArchMedium, + RoundedThin, + UpThin, + MediumArchRoundedMedium, + RoundedLarge, + UpLarge, + FlatAngledLargeInverted, + MediumArchFlat, + AngledThin, + HorizontalLarge, + HighArchFlat, + Flat, + MediumArchLarge, + LowArchThin, + RoundedThinInverted, + HighArchLarge, + Hairy, + Dotted, + None, + + Min = 0, + Max = 23 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs new file mode 100644 index 00000000..551f053d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineColor : byte + { + Beige, + WarmBeige, + Natural, + Honey, + Chestnut, + Porcelain, + Ivory, + WarmIvory, + Almond, + Espresso, + + Min = 0, + Max = 9 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs new file mode 100644 index 00000000..af6d7276 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineMake : byte + { + None, + CheekPorcelain, + CheekNatural, + EyeShadowBlue, + CheekBlushPorcelain, + CheekBlushNatural, + CheekPorcelainEyeShadowBlue, + CheekPorcelainEyeShadowNatural, + CheekBlushPorcelainEyeShadowEspresso, + Freckles, + LionsManeBeard, + StubbleBeard, + + Min = 0, + Max = 11 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs new file mode 100644 index 00000000..fe27636f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineType : byte + { + Sharp, + Rounded, + SharpRounded, + SharpRoundedSmall, + Large, + LargeRounded, + SharpSmall, + Flat, + Bump, + Angular, + FlatRounded, + AngularSmall, + + Min = 0, + Max = 11 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs new file mode 100644 index 00000000..afb75dd8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineWrinkle : byte + { + None, + TearTroughs, + FacialPain, + Cheeks, + Folds, + UnderTheEyes, + SplitChin, + Chin, + BrowDroop, + MouthFrown, + CrowsFeet, + FoldsCrowsFrown, + + Min = 0, + Max = 11 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs new file mode 100644 index 00000000..d1d86f16 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FontRegion : byte + { + Standard, + China, + Korea, + Taiwan + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs new file mode 100644 index 00000000..75f9a745 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Gender : byte + { + Male, + Female, + All, + + Min = 0, + Max = 1 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs new file mode 100644 index 00000000..ccfed0f6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum GlassType : byte + { + None, + Oval, + Wayfarer, + Rectangle, + TopRimless, + Rounded, + Oversized, + CatEye, + Square, + BottomRimless, + SemiOpaqueRounded, + SemiOpaqueCatEye, + SemiOpaqueOval, + SemiOpaqueRectangle, + SemiOpaqueAviator, + OpaqueRounded, + OpaqueCatEye, + OpaqueOval, + OpaqueRectangle, + OpaqueAviator, + + Min = 0, + Max = 19 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs new file mode 100644 index 00000000..2f7f1d73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum HairFlip : byte + { + Left, + Right, + + Min = 0, + Max = 1 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs new file mode 100644 index 00000000..a8a611da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs @@ -0,0 +1,141 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum HairType : byte + { + NormalLong, + NormalShort, + NormalMedium, + NormalExtraLong, + NormalLongBottom, + NormalTwoPeaks, + PartingLong, + FrontLock, + PartingShort, + PartingExtraLongCurved, + PartingExtraLong, + PartingMiddleLong, + PartingSquared, + PartingLongBottom, + PeaksTop, + PeaksSquared, + PartingPeaks, + PeaksLongBottom, + Peaks, + PeaksRounded, + PeaksSide, + PeaksMedium, + PeaksLong, + PeaksRoundedLong, + PartingFrontPeaks, + PartingLongFront, + PartingLongRounded, + PartingFrontPeaksLong, + PartingExtraLongRounded, + LongRounded, + NormalUnknown1, + NormalUnknown2, + NormalUnknown3, + NormalUnknown4, + NormalUnknown5, + NormalUnknown6, + DreadLocks, + PlatedMats, + Caps, + Afro, + PlatedMatsLong, + Beanie, + Short, + ShortTopLongSide, + ShortUnknown1, + ShortUnknown2, + MilitaryParting, + Military, + ShortUnknown3, + ShortUnknown4, + ShortUnknown5, + ShortUnknown6, + NoneTop, + None, + LongUnknown1, + LongUnknown2, + LongUnknown3, + LongUnknown4, + LongUnknown5, + LongUnknown6, + LongUnknown7, + LongUnknown8, + LongUnknown9, + LongUnknown10, + LongUnknown11, + LongUnknown12, + LongUnknown13, + LongUnknown14, + LongUnknown15, + LongUnknown16, + LongUnknown17, + LongUnknown18, + LongUnknown19, + LongUnknown20, + LongUnknown21, + LongUnknown22, + LongUnknown23, + LongUnknown24, + LongUnknown25, + LongUnknown26, + LongUnknown27, + LongUnknown28, + LongUnknown29, + LongUnknown30, + LongUnknown31, + LongUnknown32, + LongUnknown33, + LongUnknown34, + LongUnknown35, + LongUnknown36, + LongUnknown37, + LongUnknown38, + LongUnknown39, + LongUnknown40, + LongUnknown41, + LongUnknown42, + LongUnknown43, + LongUnknown44, + LongUnknown45, + LongUnknown46, + LongUnknown47, + LongUnknown48, + LongUnknown49, + LongUnknown50, + LongUnknown51, + LongUnknown52, + LongUnknown53, + LongUnknown54, + LongUnknown55, + LongUnknown56, + LongUnknown57, + LongUnknown58, + LongUnknown59, + LongUnknown60, + LongUnknown61, + LongUnknown62, + LongUnknown63, + LongUnknown64, + LongUnknown65, + LongUnknown66, + TwoMediumFrontStrandsOneLongBackPonyTail, + TwoFrontStrandsLongBackPonyTail, + PartingFrontTwoLongBackPonyTails, + TwoFrontStrandsOneLongBackPonyTail, + LongBackPonyTail, + LongFrontTwoLongBackPonyTails, + StrandsTwoShortSidedPonyTails, + TwoMediumSidedPonyTails, + ShortFrontTwoBackPonyTails, + TwoShortSidedPonyTails, + TwoLongSidedPonyTails, + LongFrontTwoBackPonyTails, + + Min = 0, + Max = 131 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs new file mode 100644 index 00000000..94c65b23 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + interface IElement + { + void SetFromStoreData(StoreData storeData); + + void SetSource(Source source); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs new file mode 100644 index 00000000..a6552a57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + interface IStoredData<T> : IElement, IEquatable<T> where T : notnull + { + byte Type { get; } + + CreateId CreateId { get; } + + ResultCode InvalidData { get; } + + bool IsValid(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs new file mode 100644 index 00000000..12cb6dc3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MoleType : byte + { + None, + OneDot, + + Min = 0, + Max = 1 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs new file mode 100644 index 00000000..2a8e7a00 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MouthType : byte + { + Neutral, + NeutralLips, + Smile, + SmileStroke, + SmileTeeth, + LipsSmall, + LipsLarge, + Wave, + WaveAngrySmall, + NeutralStrokeLarge, + TeethSurprised, + LipsExtraLarge, + LipsUp, + NeutralDown, + Surprised, + TeethMiddle, + NeutralStroke, + LipsExtraSmall, + Malicious, + LipsDual, + NeutralComma, + NeutralUp, + TeethLarge, + WaveAngry, + LipsSexy, + SmileInverted, + LipsSexyOutline, + SmileRounded, + LipsTeeth, + NeutralOpen, + TeethRounded, + WaveAngrySmallInverted, + NeutralCommaInverted, + TeethFull, + SmileDownLine, + Kiss, + + Min = 0, + Max = 35 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs new file mode 100644 index 00000000..a15382dd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MustacheType : byte + { + None, + Walrus, + Pencil, + Horseshoe, + Normal, + Toothbrush, + + Min = 0, + Max = 5 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs new file mode 100644 index 00000000..677d81b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs @@ -0,0 +1,120 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 2, Size = SizeConst)] + struct Nickname : IEquatable<Nickname> + { + public const int CharCount = 10; + private const int SizeConst = (CharCount + 1) * 2; + + private byte _storage; + + public static Nickname Default => FromString("no name"); + public static Nickname Question => FromString("???"); + + public Span<byte> Raw => MemoryMarshal.CreateSpan(ref _storage, SizeConst); + + private ReadOnlySpan<ushort> Characters => MemoryMarshal.Cast<byte, ushort>(Raw); + + private int GetEndCharacterIndex() + { + for (int i = 0; i < Characters.Length; i++) + { + if (Characters[i] == 0) + { + return i; + } + } + + return -1; + } + + public bool IsEmpty() + { + for (int i = 0; i < Characters.Length - 1; i++) + { + if (Characters[i] != 0) + { + return false; + } + } + + return true; + } + + public bool IsValid() + { + // Create a new unicode encoding instance with error checking enabled + UnicodeEncoding unicodeEncoding = new UnicodeEncoding(false, false, true); + + try + { + unicodeEncoding.GetString(Raw); + + return true; + } + catch (ArgumentException) + { + return false; + } + } + + public bool IsValidForFontRegion(FontRegion fontRegion) + { + // TODO: We need to extract the character tables used here, for now just assume that if it's valid Unicode, it will be valid for any font. + return IsValid(); + } + + public override string ToString() + { + return Encoding.Unicode.GetString(Raw); + } + + public static Nickname FromBytes(ReadOnlySpan<byte> data) + { + if (data.Length > SizeConst) + { + data = data.Slice(0, SizeConst); + } + + Nickname result = new Nickname(); + + data.CopyTo(result.Raw); + + return result; + } + + public static Nickname FromString(string nickname) + { + return FromBytes(Encoding.Unicode.GetBytes(nickname)); + } + + public static bool operator ==(Nickname x, Nickname y) + { + return x.Equals(y); + } + + public static bool operator !=(Nickname x, Nickname y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Nickname nickname && Equals(nickname); + } + + public bool Equals(Nickname cmpObj) + { + return Raw.SequenceEqual(cmpObj.Raw); + } + + public override int GetHashCode() + { + return HashCode.Combine(Raw.ToArray()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs new file mode 100644 index 00000000..14eda2ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs @@ -0,0 +1,254 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 0x1A98)] + struct NintendoFigurineDatabase + { + private const int DatabaseMagic = ('N' << 0) | ('F' << 8) | ('D' << 16) | ('B' << 24); + private const byte MaxMii = 100; + private const byte CurrentVersion = 1; + + private const int FigurineArraySize = MaxMii * StoreData.Size; + + private uint _magic; + + private FigurineStorageStruct _figurineStorage; + + private byte _version; + private byte _figurineCount; + private ushort _crc; + + // Set to true to allow fixing database with invalid storedata device crc instead of deleting them. + private const bool AcceptInvalidDeviceCrc = true; + + public int Length => _figurineCount; + + [StructLayout(LayoutKind.Sequential, Size = FigurineArraySize)] + private struct FigurineStorageStruct { } + + private Span<StoreData> Figurines => SpanHelpers.AsSpan<FigurineStorageStruct, StoreData>(ref _figurineStorage); + + public StoreData Get(int index) + { + return Figurines[index]; + } + + public bool IsFull() + { + return Length >= MaxMii; + } + + public bool GetIndexByCreatorId(out int index, CreateId createId) + { + for (int i = 0; i < Length; i++) + { + if (Figurines[i].CreateId == createId) + { + index = i; + + return true; + } + } + + index = -1; + + return false; + } + + public ResultCode Move(int newIndex, int oldIndex) + { + if (newIndex == oldIndex) + { + return ResultCode.NotUpdated; + } + + StoreData tmp = Figurines[oldIndex]; + + int targetLength; + int sourceIndex; + int destinationIndex; + + if (newIndex < oldIndex) + { + targetLength = oldIndex - newIndex; + sourceIndex = newIndex; + destinationIndex = newIndex + 1; + } + else + { + targetLength = newIndex - oldIndex; + sourceIndex = oldIndex + 1; + destinationIndex = oldIndex; + } + + Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength)); + + Figurines[newIndex] = tmp; + + UpdateCrc(); + + return ResultCode.Success; + } + + public void Replace(int index, StoreData storeData) + { + Figurines[index] = storeData; + + UpdateCrc(); + } + + public void Add(StoreData storeData) + { + Replace(_figurineCount++, storeData); + } + + public void Delete(int index) + { + int newCount = _figurineCount - 1; + + // If this isn't the only element in the list, move the data in it. + if (index < newCount) + { + int targetLength = newCount - index; + int sourceIndex = index + 1; + int destinationIndex = index; + + Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength)); + } + + _figurineCount = (byte)newCount; + + UpdateCrc(); + } + + public bool FixDatabase() + { + bool isBroken = false; + int i = 0; + + while (i < Length) + { + ref StoreData figurine = ref Figurines[i]; + + if (!figurine.IsValid()) + { + if (AcceptInvalidDeviceCrc && figurine.CoreData.IsValid() && figurine.IsValidDataCrc()) + { + figurine.UpdateCrc(); + } + else + { + Delete(i); + isBroken = true; + } + } + else + { + bool hasDuplicate = false; + CreateId createId = figurine.CreateId; + + for (int j = 0; j < i; j++) + { + if (Figurines[j].CreateId == createId) + { + hasDuplicate = true; + break; + } + } + + if (hasDuplicate) + { + Delete(i); + isBroken = true; + } + else + { + i++; + } + } + } + + UpdateCrc(); + + return isBroken; + } + + public ResultCode Verify() + { + if (_magic != DatabaseMagic) + { + return ResultCode.InvalidDatabaseMagic; + } + + if (_version != CurrentVersion) + { + return ResultCode.InvalidDatabaseVersion; + } + + if (!IsValidCrc()) + { + return ResultCode.InvalidCrc; + } + + if (_figurineCount > 100) + { + return ResultCode.InvalidDatabaseSize; + } + + return ResultCode.Success; + } + + public void Format() + { + _magic = DatabaseMagic; + _version = CurrentVersion; + _figurineCount = 0; + + // Fill with empty data + Figurines.Fill(new StoreData()); + + UpdateCrc(); + } + + public void CorruptDatabase() + { + UpdateCrc(); + + _crc = (ushort)~_crc; + } + + private void UpdateCrc() + { + _crc = CalculateCrc(); + } + + public bool IsValidCrc() + { + return _crc == CalculateCrc(); + } + + private ushort CalculateCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutCrc(), 0, true); + } + + public Span<byte> AsSpan() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public ReadOnlySpan<byte> AsReadOnlySpan() + { + return SpanHelpers.AsReadOnlyByteSpan(ref this); + } + + private ReadOnlySpan<byte> AsSpanWithoutCrc() + { + return AsReadOnlySpan().Slice(0, Unsafe.SizeOf<NintendoFigurineDatabase>() - 2); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs new file mode 100644 index 00000000..e898a02e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum NoseType : byte + { + Normal, + Rounded, + Dot, + Arrow, + Roman, + Triangle, + Button, + RoundedInverted, + Potato, + Grecian, + Snub, + Aquiline, + ArrowLeft, + RoundedLarge, + Hooked, + Fat, + Droopy, + ArrowLarge, + + Min = 0, + Max = 17 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs new file mode 100644 index 00000000..8cf36c27 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Race : uint + { + Black, + White, + Asian, + All + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs new file mode 100644 index 00000000..82529450 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs @@ -0,0 +1,2254 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + static class RandomMiiConstants + { + public static int[] EyeRotateTable = new int[] + { + 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04 + }; + + public static int[] EyebrowRotateTable = new int[] + { + 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, + 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05 + }; + + [Flags] + public enum BeardAndMustacheFlag : int + { + Beard = 1, + Mustache + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = ValuesArraySize)] + public struct RandomMiiValues + { + private const int ValuesArraySize = 0xbc; + + private int _firstValueByte; + + public ReadOnlySpan<int> Values => SpanHelpers.AsSpan<RandomMiiValues, int>(ref this); + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xCC)] + public struct RandomMiiData4 + { + public int Gender; + public int Age; + public int Race; + public int ValuesCount; + + private RandomMiiValues _values; + + public ReadOnlySpan<int> Values => _values.Values.Slice(0, ValuesCount); + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xC8)] + public struct RandomMiiData3 + { + private int _argument1; + private int _argument2; + + public int ValuesCount; + + private RandomMiiValues _values; + + public ReadOnlySpan<int> Values => _values.Values.Slice(0, ValuesCount); + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xC4)] + public struct RandomMiiData2 + { + private int _argument; + public int ValuesCount; + + private RandomMiiValues _values; + + public ReadOnlySpan<int> Values => _values.Values.Slice(0, ValuesCount); + } + + public static ReadOnlySpan<RandomMiiData4> RandomMiiFacelineArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiFacelineRawArray); + + public static ReadOnlySpan<RandomMiiData3> RandomMiiFacelineColorArray => MemoryMarshal.Cast<byte, RandomMiiData3>(RandomMiiFacelineColorRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiFacelineWrinkleArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiFacelineWrinkleRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiFacelineMakeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiFacelineMakeRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiHairTypeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiHairTypeRawArray); + + public static ReadOnlySpan<RandomMiiData3> RandomMiiHairColorArray => MemoryMarshal.Cast<byte, RandomMiiData3>(RandomMiiHairColorRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiEyeTypeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiEyeTypeRawArray); + + public static ReadOnlySpan<RandomMiiData2> RandomMiiEyeColorArray => MemoryMarshal.Cast<byte, RandomMiiData2>(RandomMiiEyeColorRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiEyebrowTypeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiEyebrowTypeRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiNoseTypeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiNoseTypeRawArray); + + public static ReadOnlySpan<RandomMiiData4> RandomMiiMouthTypeArray => MemoryMarshal.Cast<byte, RandomMiiData4>(RandomMiiMouthTypeRawArray); + + public static ReadOnlySpan<RandomMiiData2> RandomMiiGlassTypeArray => MemoryMarshal.Cast<byte, RandomMiiData2>(RandomMiiGlassTypeRawArray); + + #region "Random Mii Data Arrays" + + private static ReadOnlySpan<byte> RandomMiiFacelineRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiFacelineColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiFacelineWrinkleRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiFacelineMakeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiHairTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x4a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x4a, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiHairColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiEyeTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiEyeColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiEyebrowTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiNoseTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiMouthTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private static ReadOnlySpan<byte> RandomMiiGlassTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs new file mode 100644 index 00000000..1ded636a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Source : int + { + Database, + Default + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs new file mode 100644 index 00000000..d51dce87 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [Flags] + enum SourceFlag : int + { + Database = 1 << Source.Database, + Default = 1 << Source.Default, + All = Database | Default + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs new file mode 100644 index 00000000..7fe13238 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] + struct SpecialMiiKeyCode + { + private const uint SpecialMiiMagic = 0xA523B78F; + + public uint RawValue; + + public bool IsEnabledSpecialMii() + { + return RawValue == SpecialMiiMagic; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs new file mode 100644 index 00000000..8411693f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs @@ -0,0 +1,230 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] + struct StoreData : IStoredData<StoreData> + { + public const int Size = 0x44; + + public CoreData CoreData; + private CreateId _createId; + public ushort DataCrc; + public ushort DeviceCrc; + + public byte Type => CoreData.Type; + + public CreateId CreateId => _createId; + + public ResultCode InvalidData => ResultCode.InvalidStoreData; + + private void UpdateDataCrc() + { + DataCrc = CalculateDataCrc(); + } + + private void UpdateDeviceCrc() + { + DeviceCrc = CalculateDeviceCrc(); + } + + public void UpdateCrc() + { + UpdateDataCrc(); + UpdateDeviceCrc(); + } + + public bool IsSpecial() + { + return CoreData.Type == 1; + } + + public bool IsValid() + { + return CoreData.IsValid() && IsValidDataCrc() && IsValidDeviceCrc(); + } + + public bool IsValidDataCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), 0, false) == 0; + } + + public bool IsValidDeviceCrc() + { + UInt128 deviceId = Helper.GetDeviceId(); + + ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false); + + return Helper.CalculateCrc16(AsSpan(), deviceIdCrc16, false) == 0; + } + + private ushort CalculateDataCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), 0, true); + } + + private ushort CalculateDeviceCrc() + { + UInt128 deviceId = Helper.GetDeviceId(); + + ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false); + + return Helper.CalculateCrc16(AsSpan(), deviceIdCrc16, true); + } + + private ReadOnlySpan<byte> AsSpan() + { + return SpanHelpers.AsReadOnlyByteSpan(ref this); + } + + private ReadOnlySpan<byte> AsSpanWithoutDeviceCrc() + { + return AsSpan().Slice(0, Size - 2); + } + + public static StoreData BuildDefault(UtilityImpl utilImpl, uint index) + { + StoreData result = new StoreData + { + _createId = utilImpl.MakeCreateId() + }; + + CoreData coreData = new CoreData(); + + DefaultMii template = DefaultMii.GetDefaultMii(index); + + coreData.SetDefault(); + + coreData.Nickname = template.Nickname; + coreData.FontRegion = (FontRegion)template.FontRegion; + coreData.FavoriteColor = (byte)template.FavoriteColor; + coreData.Gender = (Gender)template.Gender; + coreData.Height = (byte)template.Height; + coreData.Build = (byte)template.Build; + coreData.Type = (byte)template.Type; + coreData.RegionMove = (byte)template.RegionMove; + coreData.FacelineType = (FacelineType)template.FacelineType; + coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[template.FacelineColorVer3]; + coreData.FacelineWrinkle = (FacelineWrinkle)template.FacelineWrinkle; + coreData.FacelineMake = (FacelineMake)template.FacelineMake; + coreData.HairType = (HairType)template.HairType; + coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[template.HairColorVer3]; + coreData.HairFlip = (HairFlip)template.HairFlip; + coreData.EyeType = (EyeType)template.EyeType; + coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[template.EyeColorVer3]; + coreData.EyeScale = (byte)template.EyeScale; + coreData.EyeAspect = (byte)template.EyeAspect; + coreData.EyeRotate = (byte)template.EyeRotate; + coreData.EyeX = (byte)template.EyeX; + coreData.EyeY = (byte)template.EyeY; + coreData.EyebrowType = (EyebrowType)template.EyebrowType; + coreData.EyebrowColor = (CommonColor)Helper.Ver3HairColorTable[template.EyebrowColorVer3]; + coreData.EyebrowScale = (byte)template.EyebrowScale; + coreData.EyebrowAspect = (byte)template.EyebrowAspect; + coreData.EyebrowRotate = (byte)template.EyebrowRotate; + coreData.EyebrowX = (byte)template.EyebrowX; + coreData.EyebrowY = (byte)template.EyebrowY; + coreData.NoseType = (NoseType)template.NoseType; + coreData.NoseScale = (byte)template.NoseScale; + coreData.NoseY = (byte)template.NoseY; + coreData.MouthType = (MouthType)template.MouthType; + coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[template.MouthColorVer3]; + coreData.MouthScale = (byte)template.MouthScale; + coreData.MouthAspect = (byte)template.MouthAspect; + coreData.MouthY = (byte)template.MouthY; + coreData.BeardColor = (CommonColor)Helper.Ver3HairColorTable[template.BeardColorVer3]; + coreData.BeardType = (BeardType)template.BeardType; + coreData.MustacheType = (MustacheType)template.MustacheType; + coreData.MustacheScale = (byte)template.MustacheScale; + coreData.MustacheY = (byte)template.MustacheY; + coreData.GlassType = (GlassType)template.GlassType; + coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[template.GlassColorVer3]; + coreData.GlassScale = (byte)template.GlassScale; + coreData.GlassY = (byte)template.GlassY; + coreData.MoleType = (MoleType)template.MoleType; + coreData.MoleScale = (byte)template.MoleScale; + coreData.MoleX = (byte)template.MoleX; + coreData.MoleY = (byte)template.MoleY; + + result.CoreData = coreData; + + result.UpdateCrc(); + + return result; + } + + public static StoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race) + { + return BuildFromCoreData(utilImpl, CoreData.BuildRandom(utilImpl, age, gender, race)); + } + + public static StoreData BuildFromCoreData(UtilityImpl utilImpl, CoreData coreData) + { + StoreData result = new StoreData + { + CoreData = coreData, + _createId = utilImpl.MakeCreateId() + }; + + result.UpdateCrc(); + + return result; + } + + public void SetFromStoreData(StoreData storeData) + { + this = storeData; + } + + public void SetSource(Source source) + { + // Only implemented for Element variants. + } + + public static bool operator ==(StoreData x, StoreData y) + { + return x.Equals(y); + } + + public static bool operator !=(StoreData x, StoreData y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is StoreData storeData && Equals(storeData); + } + + public bool Equals(StoreData cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= CreateId == cmpObj.CreateId; + result &= CoreData == cmpObj.CoreData; + result &= DataCrc == cmpObj.DataCrc; + result &= DeviceCrc == cmpObj.DeviceCrc; + + return result; + } + + public override int GetHashCode() + { + HashCode hashCode = new HashCode(); + + hashCode.Add(CreateId); + hashCode.Add(CoreData); + hashCode.Add(DataCrc); + hashCode.Add(DeviceCrc); + + return hashCode.ToHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs new file mode 100644 index 00000000..8d3e96be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct StoreDataElement : IElement + { + public StoreData StoreData; + public Source Source; + + public void SetFromStoreData(StoreData storeData) + { + StoreData = storeData; + } + + public void SetSource(Source source) + { + Source = source; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs new file mode 100644 index 00000000..70bb348b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct Ver3StoreData + { + public const int Size = 0x60; + + private byte _storage; + + public Span<byte> Storage => MemoryMarshal.CreateSpan(ref _storage, Size); + + // TODO: define all getters/setters + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs new file mode 100644 index 00000000..30b201f6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs @@ -0,0 +1,75 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Time; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class UtilityImpl + { + private uint _x; + private uint _y; + private uint _z; + private uint _w; + + public UtilityImpl(ITickSource tickSource) + { + _x = 123456789; + _y = 362436069; + + TimeSpanType time = TimeManager.Instance.TickBasedSteadyClock.GetCurrentRawTimePoint(tickSource); + + _w = (uint)(time.NanoSeconds & uint.MaxValue); + _z = (uint)((time.NanoSeconds >> 32) & uint.MaxValue); + } + + private uint GetRandom() + { + uint t = (_x ^ (_x << 11)); + + _x = _y; + _y = _z; + _z = _w; + _w = (_w ^ (_w >> 19)) ^ (t ^ (t >> 8)); + + return _w; + } + + public int GetRandom(int end) + { + return (int)GetRandom((uint)end); + } + + public uint GetRandom(uint end) + { + uint random = GetRandom(); + + return random - random / end * end; + } + + public uint GetRandom(uint start, uint end) + { + uint random = GetRandom(); + + return random - random / (1 - start + end) * (1 - start + end) + start; + } + + public int GetRandom(int start, int end) + { + return (int)GetRandom((uint)start, (uint)end); + } + + public CreateId MakeCreateId() + { + UInt128 value = UInt128Utils.CreateRandom(); + + // Ensure the random ID generated is valid as a create id. + value &= ~new UInt128(0xC0, 0); + value |= new UInt128(0x80, 0); + + return new CreateId(value); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs new file mode 100644 index 00000000..fac42555 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs @@ -0,0 +1,196 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Mm.Types; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Mm +{ + [Service("mm:u")] + class IRequest : IpcService + { + private static object _sessionListLock = new object(); + private static List<MultiMediaSession> _sessionList = new List<MultiMediaSession>(); + + private static uint _uniqueId = 1; + + public IRequest(ServiceCtx context) { } + + [CommandCmif(0)] + // InitializeOld(u32, u32, u32) + public ResultCode InitializeOld(ServiceCtx context) + { + MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); + int fgmId = context.RequestData.ReadInt32(); + bool isAutoClearEvent = context.RequestData.ReadInt32() != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent }); + + Register(operationType, fgmId, isAutoClearEvent); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // FinalizeOld(u32) + public ResultCode FinalizeOld(ServiceCtx context) + { + MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType }); + + lock (_sessionListLock) + { + _sessionList.Remove(GetSessionByType(operationType)); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // SetAndWaitOld(u32, u32, u32) + public ResultCode SetAndWaitOld(ServiceCtx context) + { + MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); + uint frequenceHz = context.RequestData.ReadUInt32(); + int timeout = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, frequenceHz, timeout }); + + lock (_sessionListLock) + { + GetSessionByType(operationType)?.SetAndWait(frequenceHz, timeout); + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetOld(u32) -> u32 + public ResultCode GetOld(ServiceCtx context) + { + MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType }); + + lock (_sessionListLock) + { + MultiMediaSession session = GetSessionByType(operationType); + + uint currentValue = session == null ? 0 : session.CurrentValue; + + context.ResponseData.Write(currentValue); + } + + return ResultCode.Success; + } + + [CommandCmif(4)] + // Initialize(u32, u32, u32) -> u32 + public ResultCode Initialize(ServiceCtx context) + { + MultiMediaOperationType operationType = (MultiMediaOperationType)context.RequestData.ReadUInt32(); + int fgmId = context.RequestData.ReadInt32(); + bool isAutoClearEvent = context.RequestData.ReadInt32() != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { operationType, fgmId, isAutoClearEvent }); + + uint id = Register(operationType, fgmId, isAutoClearEvent); + + context.ResponseData.Write(id); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Finalize(u32) + public ResultCode Finalize(ServiceCtx context) + { + uint id = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id }); + + lock (_sessionListLock) + { + _sessionList.Remove(GetSessionById(id)); + } + + return ResultCode.Success; + } + + [CommandCmif(6)] + // SetAndWait(u32, u32, u32) + public ResultCode SetAndWait(ServiceCtx context) + { + uint id = context.RequestData.ReadUInt32(); + uint frequenceHz = context.RequestData.ReadUInt32(); + int timeout = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id, frequenceHz, timeout }); + + lock (_sessionListLock) + { + GetSessionById(id)?.SetAndWait(frequenceHz, timeout); + } + + return ResultCode.Success; + } + + [CommandCmif(7)] + // Get(u32) -> u32 + public ResultCode Get(ServiceCtx context) + { + uint id = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { id }); + + lock (_sessionListLock) + { + MultiMediaSession session = GetSessionById(id); + + uint currentValue = session == null ? 0 : session.CurrentValue; + + context.ResponseData.Write(currentValue); + } + + return ResultCode.Success; + } + + private MultiMediaSession GetSessionById(uint id) + { + foreach (MultiMediaSession session in _sessionList) + { + if (session.Id == id) + { + return session; + } + } + + return null; + } + + private MultiMediaSession GetSessionByType(MultiMediaOperationType type) + { + foreach (MultiMediaSession session in _sessionList) + { + if (session.Type == type) + { + return session; + } + } + + return null; + } + + private uint Register(MultiMediaOperationType type, int fgmId, bool isAutoClearEvent) + { + lock (_sessionListLock) + { + // Nintendo ignore the fgm id as the other interfaces were deprecated. + MultiMediaSession session = new MultiMediaSession(_uniqueId++, type, isAutoClearEvent); + + _sessionList.Add(session); + + return session.Id; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs new file mode 100644 index 00000000..2742af6c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mm.Types +{ + enum MultiMediaOperationType : uint + { + Ram = 2, + NvEnc = 5, + NvDec = 6, + NvJpg = 7 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs new file mode 100644 index 00000000..a6723eca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Mm.Types +{ + class MultiMediaSession + { + public MultiMediaOperationType Type { get; } + + public bool IsAutoClearEvent { get; } + public uint Id { get; } + public uint CurrentValue { get; private set; } + + public MultiMediaSession(uint id, MultiMediaOperationType type, bool isAutoClearEvent) + { + Type = type; + Id = id; + IsAutoClearEvent = isAutoClearEvent; + CurrentValue = 0; + } + + public void SetAndWait(uint value, int timeout) + { + CurrentValue = value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs new file mode 100644 index 00000000..c2a4345c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Account.Acc; + +namespace Ryujinx.HLE.HOS.Services.Mnpp +{ + [Service("mnpp:app")] // 13.0.0+ + class IServiceForApplication : IpcService + { + public IServiceForApplication(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(pid) + public ResultCode Initialize(ServiceCtx context) + { + // Pid placeholder + context.RequestData.ReadInt64(); + ulong pid = context.Request.HandleDesc.PId; + + // TODO: Service calls set:sys GetPlatformRegion. + // If the result == 1 (China) it calls arp:r GetApplicationInstanceId and GetApplicationLaunchProperty to get the title id and store it internally. + // If not, it does nothing. + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { pid }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // SendRawTelemetryData(nn::account::Uid user_id, buffer<bytes, 5> title_id) + public ResultCode SendRawTelemetryData(ServiceCtx context) + { + ulong titleIdInputPosition = context.Request.SendBuff[0].Position; + ulong titleIdInputSize = context.Request.SendBuff[0].Size; + + UserId userId = context.RequestData.ReadStruct<UserId>(); + + // TODO: Service calls set:sys GetPlatformRegion. + // If the result != 1 (China) it returns ResultCode.Success. + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + if (titleIdInputSize <= 64) + { + string titleId = MemoryHelper.ReadAsciiString(context.Memory, titleIdInputPosition, (long)titleIdInputSize); + + // TODO: The service stores the titleId internally and seems proceed to some telemetry for China, which is not needed here. + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId, titleId }); + + return ResultCode.Success; + } + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId }); + + return ResultCode.InvalidBufferSize; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs new file mode 100644 index 00000000..dfc39a73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Mnpp +{ + enum ResultCode + { + ModuleId = 239, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (100 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (101 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs new file mode 100644 index 00000000..7f05d9be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm +{ + [Service("ncm")] + class IContentManager : IpcService + { + public IContentManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs new file mode 100644 index 00000000..318ad30e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs @@ -0,0 +1,22 @@ +using LibHac.Ncm; +using Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + [Service("lr")] + class ILocationResolverManager : IpcService + { + public ILocationResolverManager(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenLocationResolver() + public ResultCode OpenLocationResolver(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); + + MakeObject(context, new ILocationResolver(storageId)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs new file mode 100644 index 00000000..55b49bce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs @@ -0,0 +1,254 @@ +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.HLE.FileSystem; +using System.Text; + +using static Ryujinx.HLE.Utilities.StringUtils; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager +{ + class ILocationResolver : IpcService + { + private StorageId _storageId; + + public ILocationResolver(StorageId storageId) + { + _storageId = storageId; + } + + [CommandCmif(0)] + // ResolveProgramPath(u64 titleId) + public ResultCode ResolveProgramPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Program)) + { + return ResultCode.Success; + } + else + { + return ResultCode.ProgramLocationEntryNotFound; + } + } + + [CommandCmif(1)] + // RedirectProgramPath(u64 titleId) + public ResultCode RedirectProgramPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 0, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // ResolveApplicationControlPath(u64 titleId) + public ResultCode ResolveApplicationControlPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Control)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(3)] + // ResolveApplicationHtmlDocumentPath(u64 titleId) + public ResultCode ResolveApplicationHtmlDocumentPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(4)] + // ResolveDataPath(u64 titleId) + public ResultCode ResolveDataPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Data) || ResolvePath(context, titleId, NcaContentType.PublicData)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(5)] + // RedirectApplicationControlPath(u64 titleId) + public ResultCode RedirectApplicationControlPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Control); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // RedirectApplicationHtmlDocumentPath(u64 titleId) + public ResultCode RedirectApplicationHtmlDocumentPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // ResolveApplicationLegalInformationPath(u64 titleId) + public ResultCode ResolveApplicationLegalInformationPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(8)] + // RedirectApplicationLegalInformationPath(u64 titleId) + public ResultCode RedirectApplicationLegalInformationPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // Refresh() + public ResultCode Refresh(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetProgramNcaPath2(u64 titleId) + public ResultCode SetProgramNcaPath2(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ClearLocationResolver2() + public ResultCode ClearLocationResolver2(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // DeleteProgramNcaPath(u64 titleId) + public ResultCode DeleteProgramNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // DeleteControlNcaPath(u64 titleId) + public ResultCode DeleteControlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Control); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // DeleteDocHtmlNcaPath(u64 titleId) + public ResultCode DeleteDocHtmlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(15)] + // DeleteInfoHtmlNcaPath(u64 titleId) + public ResultCode DeleteInfoHtmlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + private void RedirectPath(ServiceCtx context, ulong titleId, int flag, NcaContentType contentType) + { + string contentPath = ReadUtf8String(context); + LocationEntry newLocation = new LocationEntry(contentPath, flag, titleId, contentType); + + context.Device.System.ContentManager.RedirectLocation(newLocation, _storageId); + } + + private bool ResolvePath(ServiceCtx context, ulong titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Program); + + if (!string.IsNullOrWhiteSpace(contentPath)) + { + ulong position = context.Request.RecvListBuff[0].Position; + ulong size = context.Request.RecvListBuff[0].Size; + + byte[] contentPathBuffer = Encoding.UTF8.GetBytes(contentPath); + + context.Memory.Write(position, contentPathBuffer); + } + else + { + return false; + } + + return true; + } + + private void DeleteContentPath(ServiceCtx context, ulong titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Manual); + + contentManager.ClearEntry(titleId, NcaContentType.Manual, _storageId); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs new file mode 100644 index 00000000..d21fe634 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + enum ResultCode + { + ModuleId = 8, + ErrorCodeShift = 9, + + Success = 0, + + ProgramLocationEntryNotFound = (2 << ErrorCodeShift) | ModuleId, + InvalidContextForControlLocation = (3 << ErrorCodeShift) | ModuleId, + StorageNotFound = (4 << ErrorCodeShift) | ModuleId, + AccessDenied = (5 << ErrorCodeShift) | ModuleId, + OfflineManualHTMLLocationEntryNotFound = (6 << ErrorCodeShift) | ModuleId, + TitleIsNotRegistered = (7 << ErrorCodeShift) | ModuleId, + ControlLocationEntryForHostNotFound = (8 << ErrorCodeShift) | ModuleId, + LegalInfoHTMLLocationEntryNotFound = (9 << ErrorCodeShift) | ModuleId, + ProgramLocationForDebugEntryNotFound = (10 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs new file mode 100644 index 00000000..7ea89b20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.News +{ + [Service("news:a")] + [Service("news:c")] + [Service("news:m")] + [Service("news:p")] + [Service("news:v")] + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs new file mode 100644 index 00000000..33932568 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:am")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs new file mode 100644 index 00000000..ef90b6ad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.NfcManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateSystemInterface() -> object<nn::nfc::detail::ISystem> + public ResultCode CreateSystemInterface(ServiceCtx context) + { + MakeObject(context, new INfc(NfcPermissionLevel.System)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs new file mode 100644 index 00000000..97959a62 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.NfcManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserInterface() -> object<nn::nfc::detail::IUser> + public ResultCode CreateUserInterface(ServiceCtx context) + { + MakeObject(context, new INfc(NfcPermissionLevel.User)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs new file mode 100644 index 00000000..cc3cd3aa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare +{ + [Service("nfc:mf:u")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs new file mode 100644 index 00000000..b091aabf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + class INfc : IpcService + { + private NfcPermissionLevel _permissionLevel; + private State _state; + + public INfc(NfcPermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + _state = State.NonInitialized; + } + + [CommandCmif(0)] + [CommandCmif(400)] // 4.0.0+ + // Initialize(u64, u64, pid, buffer<unknown, 5>) + public ResultCode Initialize(ServiceCtx context) + { + _state = State.Initialized; + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + [CommandCmif(401)] // 4.0.0+ + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + _state = State.NonInitialized; + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + [CommandCmif(402)] // 4.0.0+ + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [CommandCmif(3)] + [CommandCmif(403)] // 4.0.0+ + // IsNfcEnabled() -> b8 + public ResultCode IsNfcEnabled(ServiceCtx context) + { + // NOTE: Write false value here could make nfp service not called. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs new file mode 100644 index 00000000..39babc73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + enum NfcPermissionLevel + { + User, + System + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs new file mode 100644 index 00000000..85f99950 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + enum State + { + NonInitialized, + Initialized + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs new file mode 100644 index 00000000..e75f6200 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System.Text.Json.Serialization; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [JsonSerializable(typeof(VirtualAmiiboFile))] + internal partial class AmiiboJsonSerializerContext : JsonSerializerContext + { + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs new file mode 100644 index 00000000..fc454473 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:dbg")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateDebugInterface() -> object<nn::nfp::detail::IDebug> + public ResultCode CreateDebugInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.Debug)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs new file mode 100644 index 00000000..3fcf7a87 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateSystemInterface() -> object<nn::nfp::detail::ISystem> + public ResultCode CreateSystemInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.System)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs new file mode 100644 index 00000000..93da8419 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserInterface() -> object<nn::nfp::detail::IUser> + public ResultCode CreateUserInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.User)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs new file mode 100644 index 00000000..e25a2972 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs @@ -0,0 +1,1000 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers.Binary; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + class INfp : IpcService + { + private ulong _appletResourceUserId; + private ulong _mcuVersionData; + private byte[] _mcuData; + + private State _state = State.NonInitialized; + + private KEvent _availabilityChangeEvent; + + private CancellationTokenSource _cancelTokenSource; + + private NfpPermissionLevel _permissionLevel; + + public INfp(NfpPermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [CommandCmif(0)] + // Initialize(u64, u64, pid, buffer<unknown, 5>) + public ResultCode Initialize(ServiceCtx context) + { + _appletResourceUserId = context.RequestData.ReadUInt64(); + _mcuVersionData = context.RequestData.ReadUInt64(); + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + _mcuData = new byte[inputSize]; + + context.Memory.Read(inputPosition, _mcuData); + + // TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined. + + // TODO: Handle this in a controller class directly. + // Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc(). + NfpDevice devicePlayer1 = new NfpDevice + { + NpadIdType = NpadIdType.Player1, + Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1), + State = NfpDeviceState.Initialized + }; + + context.Device.System.NfpDevices.Add(devicePlayer1); + + // TODO: It mounts 0x8000000000000020 save data and stores a random generate value inside. Usage of the data needs to be determined. + + _state = State.Initialized; + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + if (_state == State.Initialized) + { + if (_cancelTokenSource != null) + { + _cancelTokenSource.Cancel(); + } + + // NOTE: All events are destroyed here. + context.Device.System.NfpDevices.Clear(); + + _state = State.NonInitialized; + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // ListDevices() -> (u32, buffer<unknown, 0xa>) + public ResultCode ListDevices(ServiceCtx context) + { + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + ulong outputSize = context.Request.RecvListBuff[0].Size; + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + if (CheckNfcIsEnabled() == ResultCode.Success) + { + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfpDevices[i].Handle); + } + + context.ResponseData.Write(context.Device.System.NfpDevices.Count); + } + else + { + context.ResponseData.Write(0); + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // StartDetection(bytes<8, 4>) + public ResultCode StartDetection(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag; + + break; + } + } + + _cancelTokenSource = new CancellationTokenSource(); + + Task.Run(() => + { + while (true) + { + if (_cancelTokenSource.Token.IsCancellationRequested) + { + break; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + context.Device.System.NfpDevices[i].SignalActivate(); + Thread.Sleep(125); // NOTE: Simulate amiibo scanning delay. + context.Device.System.NfpDevices[i].SignalDeactivate(); + + break; + } + } + } + }, _cancelTokenSource.Token); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // StopDetection(bytes<8, 4>) + public ResultCode StopDetection(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (_cancelTokenSource != null) + { + _cancelTokenSource.Cancel(); + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfpDevices[i].State = NfpDeviceState.Initialized; + + break; + } + } + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Mount(bytes<8, 4>, u32, u32) + public ResultCode Mount(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + DeviceType deviceType = (DeviceType)context.RequestData.ReadUInt32(); + MountTarget mountTarget = (MountTarget)context.RequestData.ReadUInt32(); + + if (deviceType != 0) + { + return ResultCode.WrongArgument; + } + + if (((uint)mountTarget & 3) == 0) + { + return ResultCode.WrongArgument; + } + + // TODO: Found how the MountTarget is handled. + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + // NOTE: This mount the amiibo data, which isn't needed in our case. + + context.Device.System.NfpDevices[i].State = NfpDeviceState.TagMounted; + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(6)] + // Unmount(bytes<8, 4>) + public ResultCode Unmount(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + // NOTE: This mount the amiibo data, which isn't needed in our case. + + context.Device.System.NfpDevices[i].State = NfpDeviceState.TagFound; + + resultCode = ResultCode.Success; + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(7)] + // OpenApplicationArea(bytes<8, 4>, u32) + public ResultCode OpenApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + uint applicationAreaId = context.RequestData.ReadUInt32(); + + bool isOpened = false; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + isOpened = VirtualAmiibo.OpenApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + if (!isOpened) + { + resultCode = ResultCode.ApplicationAreaIsNull; + } + + return resultCode; + } + + [CommandCmif(8)] + // GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>) + public ResultCode GetApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + uint size = 0; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + byte[] applicationArea = VirtualAmiibo.GetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId); + + context.Memory.Write(outputPosition, applicationArea); + + size = (uint)applicationArea.Length; + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + } + } + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (size == 0) + { + return ResultCode.ApplicationAreaIsNull; + } + + context.ResponseData.Write(size); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>) + public ResultCode SetApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read(inputPosition, applicationArea); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + VirtualAmiibo.SetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationArea); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(10)] + // Flush(bytes<8, 4>) + public ResultCode Flush(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + // NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case. + + return ResultCode.Success; + } + + [CommandCmif(11)] + // Restore(bytes<8, 4>) + public ResultCode Restore(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(12)] + // CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>) + public ResultCode CreateApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + uint applicationAreaId = context.RequestData.ReadUInt32(); + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read(inputPosition, applicationArea); + + bool isCreated = false; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + isCreated = VirtualAmiibo.CreateApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId, applicationArea); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + if (!isCreated) + { + resultCode = ResultCode.ApplicationAreaIsNull; + } + + return resultCode; + } + + [CommandCmif(13)] + // GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a> + public ResultCode GetTagInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted || context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + byte[] Uuid = VirtualAmiibo.GenerateUuid(context.Device.System.NfpDevices[i].AmiiboId, context.Device.System.NfpDevices[i].UseRandomUuid); + + if (Uuid.Length > AmiiboConstants.UuidMaxLength) + { + throw new ArgumentOutOfRangeException(); + } + + TagInfo tagInfo = new TagInfo + { + UuidLength = (byte)Uuid.Length, + Reserved1 = new Array21<byte>(), + Protocol = uint.MaxValue, // All Protocol + TagType = uint.MaxValue, // All Type + Reserved2 = new Array6<byte>() + }; + + Uuid.CopyTo(tagInfo.Uuid.AsSpan()); + + context.Memory.Write(outputPosition, tagInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(14)] + // GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a> + public ResultCode GetRegisterInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<RegisterInfo>()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<RegisterInfo>()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + RegisterInfo registerInfo = VirtualAmiibo.GetRegisterInfo( + context.Device.System.TickSource, + context.Device.System.NfpDevices[i].AmiiboId, + context.Device.System.AccountManager.LastOpenedUser.Name); + + context.Memory.Write(outputPosition, registerInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(15)] + // GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a> + public ResultCode GetCommonInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<CommonInfo>()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<CommonInfo>()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + CommonInfo commonInfo = VirtualAmiibo.GetCommonInfo(context.Device.System.NfpDevices[i].AmiiboId); + + context.Memory.Write(outputPosition, commonInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(16)] + // GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a> + public ResultCode GetModelInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ModelInfo>()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<ModelInfo>()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + ModelInfo modelInfo = new ModelInfo + { + Reserved = new Array57<byte>() + }; + + modelInfo.CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(0, 4), NumberStyles.HexNumber)); + modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(4, 2), NumberStyles.HexNumber); + modelInfo.Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(12, 2), NumberStyles.HexNumber); + modelInfo.ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(8, 4), NumberStyles.HexNumber); + modelInfo.Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(6, 2), NumberStyles.HexNumber); + + context.Memory.Write(outputPosition, modelInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(17)] + // AttachActivateEvent(bytes<8, 4>) -> handle<copy> + public ResultCode AttachActivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(18)] + // AttachDeactivateEvent(bytes<8, 4>) -> handle<copy> + public ResultCode AttachDeactivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(19)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetDeviceState(bytes<8, 4>) -> u32 + public ResultCode GetDeviceState(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + if (context.Device.System.NfpDevices[i].State > NfpDeviceState.Finalized) + { + throw new ArgumentOutOfRangeException(); + } + + context.ResponseData.Write((uint)context.Device.System.NfpDevices[i].State); + + return ResultCode.Success; + } + } + + context.ResponseData.Write((uint)NfpDeviceState.Unavailable); + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(21)] + // GetNpadId(bytes<8, 4>) -> u32 + public ResultCode GetNpadId(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle)); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(22)] + // GetApplicationAreaSize() -> u32 + public ResultCode GetApplicationAreaSize(ServiceCtx context) + { + context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize); + + return ResultCode.Success; + } + + [CommandCmif(23)] // 3.0.0+ + // AttachAvailabilityChangeEvent() -> handle<copy> + public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context) + { + _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(24)] // 3.0.0+ + // RecreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>) + public ResultCode RecreateApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(102)] + // GetRegisterInfo2(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a> + public ResultCode GetRegisterInfo2(ServiceCtx context) + { + // TODO: Find the differencies between IUser and ISystem/IDebug. + + if (_permissionLevel == NfpPermissionLevel.Debug || _permissionLevel == NfpPermissionLevel.System) + { + return GetRegisterInfo(context); + } + + return ResultCode.DeviceNotFound; + } + + private ResultCode CheckNfcIsEnabled() + { + // TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented. + return true ? ResultCode.Success : ResultCode.NfcDisabled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs new file mode 100644 index 00000000..b06492e6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + static class AmiiboConstants + { + public const int UuidMaxLength = 10; + public const int ApplicationAreaSize = 0xD8; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs new file mode 100644 index 00000000..a7976de9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct CommonInfo + { + public ushort LastWriteYear; + public byte LastWriteMonth; + public byte LastWriteDay; + public ushort WriteCounter; + public ushort Version; + public uint ApplicationAreaSize; + public Array52<byte> Reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs new file mode 100644 index 00000000..096522a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum DeviceType : uint + { + Amiibo + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs new file mode 100644 index 00000000..c66636ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct ModelInfo + { + public ushort CharacterId; + public byte CharacterVariant; + public byte Series; + public ushort ModelNumber; + public byte Type; + public Array57<byte> Reserved; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs new file mode 100644 index 00000000..4a145773 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum MountTarget : uint + { + Rom = 1, + Ram = 2, + All = 3 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs new file mode 100644 index 00000000..f56d33a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs @@ -0,0 +1,23 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + class NfpDevice + { + public KEvent ActivateEvent; + public KEvent DeactivateEvent; + + public void SignalActivate() => ActivateEvent.ReadableEvent.Signal(); + public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal(); + + public NfpDeviceState State = NfpDeviceState.Unavailable; + + public PlayerIndex Handle; + public NpadIdType NpadIdType; + + public string AmiiboId; + + public bool UseRandomUuid; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs new file mode 100644 index 00000000..51e1d060 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum NfpDeviceState + { + Initialized = 0, + SearchingForTag = 1, + TagFound = 2, + TagRemoved = 3, + TagMounted = 4, + Unavailable = 5, + Finalized = 6 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs new file mode 100644 index 00000000..8b84dcfe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum NfpPermissionLevel + { + Debug, + User, + System + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs new file mode 100644 index 00000000..6b30eb8e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct RegisterInfo + { + public CharInfo MiiCharInfo; + public ushort FirstWriteYear; + public byte FirstWriteMonth; + public byte FirstWriteDay; + public Array41<byte> Nickname; + public byte FontRegion; + public Array64<byte> Reserved1; + public Array58<byte> Reserved2; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs new file mode 100644 index 00000000..b38cf9e2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum State + { + NonInitialized = 0, + Initialized = 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs new file mode 100644 index 00000000..d2076b2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58)] + struct TagInfo + { + public Array10<byte> Uuid; + public byte UuidLength; + public Array21<byte> Reserved1; + public uint Protocol; + public uint TagType; + public Array6<byte> Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs new file mode 100644 index 00000000..be1877e5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + struct VirtualAmiiboFile + { + public uint FileVersion { get; set; } + public byte[] TagUuid { get; set; } + public string AmiiboId { get; set; } + public DateTime FirstWriteDate { get; set; } + public DateTime LastWriteDate { get; set; } + public ushort WriteCounter { get; set; } + public List<VirtualAmiiboApplicationArea> ApplicationAreas { get; set; } + } + + struct VirtualAmiiboApplicationArea + { + public uint ApplicationAreaId { get; set; } + public byte[] ApplicationArea { get; set; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs new file mode 100644 index 00000000..e0ccbc6d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + public enum ResultCode + { + ModuleId = 115, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, + WrongArgument = (65 << ErrorCodeShift) | ModuleId, + WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, + NfcDisabled = (80 << ErrorCodeShift) | ModuleId, + TagNotFound = (97 << ErrorCodeShift) | ModuleId, + ApplicationAreaIsNull = (128 << ErrorCodeShift) | ModuleId, + ApplicationAreaAlreadyCreated = (168 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs new file mode 100644 index 00000000..9166e87f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -0,0 +1,204 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + static class VirtualAmiibo + { + private static uint _openedApplicationAreaId; + + private static readonly AmiiboJsonSerializerContext SerializerContext = AmiiboJsonSerializerContext.Default; + + public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) + { + if (useRandomUuid) + { + return GenerateRandomUuid(); + } + + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.TagUuid.Length == 0) + { + virtualAmiiboFile.TagUuid = GenerateRandomUuid(); + + SaveAmiiboFile(virtualAmiiboFile); + } + + return virtualAmiiboFile.TagUuid; + } + + private static byte[] GenerateRandomUuid() + { + byte[] uuid = new byte[9]; + + Random.Shared.NextBytes(uuid); + + uuid[3] = (byte)(0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]); + uuid[8] = (byte)(uuid[3] ^ uuid[4] ^ uuid[5] ^ uuid[6]); + + return uuid; + } + + public static CommonInfo GetCommonInfo(string amiiboId) + { + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + + return new CommonInfo() + { + LastWriteYear = (ushort)amiiboFile.LastWriteDate.Year, + LastWriteMonth = (byte)amiiboFile.LastWriteDate.Month, + LastWriteDay = (byte)amiiboFile.LastWriteDate.Day, + WriteCounter = amiiboFile.WriteCounter, + Version = 1, + ApplicationAreaSize = AmiiboConstants.ApplicationAreaSize, + Reserved = new Array52<byte>() + }; + } + + public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiiboId, string nickname) + { + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + + UtilityImpl utilityImpl = new UtilityImpl(tickSource); + CharInfo charInfo = new CharInfo(); + + charInfo.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0)); + + charInfo.Nickname = Nickname.FromString(nickname); + + RegisterInfo registerInfo = new RegisterInfo() + { + MiiCharInfo = charInfo, + FirstWriteYear = (ushort)amiiboFile.FirstWriteDate.Year, + FirstWriteMonth = (byte)amiiboFile.FirstWriteDate.Month, + FirstWriteDay = (byte)amiiboFile.FirstWriteDate.Day, + FontRegion = 0, + Reserved1 = new Array64<byte>(), + Reserved2 = new Array58<byte>() + }; + "Ryujinx"u8.CopyTo(registerInfo.Nickname.AsSpan()); + + return registerInfo; + } + + public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId)) + { + _openedApplicationAreaId = applicationAreaId; + + return true; + } + + return false; + } + + public static byte[] GetApplicationArea(string amiiboId) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas) + { + if (applicationArea.ApplicationAreaId == _openedApplicationAreaId) + { + return applicationArea.ApplicationArea; + } + } + + return Array.Empty<byte>(); + } + + public static bool CreateApplicationArea(string amiiboId, uint applicationAreaId, byte[] applicationAreaData) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId)) + { + return false; + } + + virtualAmiiboFile.ApplicationAreas.Add(new VirtualAmiiboApplicationArea() + { + ApplicationAreaId = applicationAreaId, + ApplicationArea = applicationAreaData + }); + + SaveAmiiboFile(virtualAmiiboFile); + + return true; + } + + public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == _openedApplicationAreaId)) + { + for (int i = 0; i < virtualAmiiboFile.ApplicationAreas.Count; i++) + { + if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == _openedApplicationAreaId) + { + virtualAmiiboFile.ApplicationAreas[i] = new VirtualAmiiboApplicationArea() + { + ApplicationAreaId = _openedApplicationAreaId, + ApplicationArea = applicationAreaData + }; + + break; + } + } + + SaveAmiiboFile(virtualAmiiboFile); + } + } + + private static VirtualAmiiboFile LoadAmiiboFile(string amiiboId) + { + Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{amiiboId}.json"); + + VirtualAmiiboFile virtualAmiiboFile; + + if (File.Exists(filePath)) + { + virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, SerializerContext.VirtualAmiiboFile); + } + else + { + virtualAmiiboFile = new VirtualAmiiboFile() + { + FileVersion = 0, + TagUuid = Array.Empty<byte>(), + AmiiboId = amiiboId, + FirstWriteDate = DateTime.Now, + LastWriteDate = DateTime.Now, + WriteCounter = 0, + ApplicationAreas = new List<VirtualAmiiboApplicationArea>() + }; + + SaveAmiiboFile(virtualAmiiboFile); + } + + return virtualAmiiboFile; + } + + private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) + { + string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); + JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, SerializerContext.VirtualAmiiboFile); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs new file mode 100644 index 00000000..eacf35f3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + [Service("ngct:u")] // 9.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + + [CommandCmif(0)] + // Match(buffer<string, 9>) -> b8 + public ResultCode Match(ServiceCtx context) + { + return NgctServer.Match(context); + } + + [CommandCmif(1)] + // Filter(buffer<string, 9>) -> buffer<filtered_string, 10> + public ResultCode Filter(ServiceCtx context) + { + return NgctServer.Filter(context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs new file mode 100644 index 00000000..5ad056ba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + [Service("ngct:s")] // 9.0.0+ + class IServiceWithManagementApi : IpcService + { + public IServiceWithManagementApi(ServiceCtx context) { } + + [CommandCmif(0)] + // Match(buffer<string, 9>) -> b8 + public ResultCode Match(ServiceCtx context) + { + return NgctServer.Match(context); + } + + [CommandCmif(1)] + // Filter(buffer<string, 9>) -> buffer<filtered_string, 10> + public ResultCode Filter(ServiceCtx context) + { + return NgctServer.Filter(context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs new file mode 100644 index 00000000..8d99721e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs @@ -0,0 +1,92 @@ +using Ryujinx.Common.Logging; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + static class NgctServer + { + public static ResultCode Match(ServiceCtx context) + { + // NOTE: Service load the values of sys:set ngc.t!functionality_override_enabled and ngc.t!auto_reload_enabled in internal fields. + // Then it checks if ngc.t!functionality_override_enabled is enabled and if sys:set GetT is == 2. + // If both conditions are true, it does this following code. Since we currently stub it, it's fine to don't check settings service values. + + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + bool isMatch = false; + string text = ""; + + if (bufferSize != 0) + { + if (bufferSize > 1024) + { + isMatch = true; + } + else + { + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + text = Encoding.ASCII.GetString(buffer); + + // NOTE: Ngct use the archive 0100000000001034 which contains a words table. This is pushed on Chinese Switchs using Bcat service. + // This call check if the string match with entries in the table and return the result if there is one (or more). + // Since we don't want to hide bad words. It's fine to returns false here. + + isMatch = false; + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceNgct, new { isMatch, text }); + + context.ResponseData.Write(isMatch); + + return ResultCode.Success; + } + + public static ResultCode Filter(ServiceCtx context) + { + // NOTE: Service load the values of sys:set ngc.t!functionality_override_enabled and ngc.t!auto_reload_enabled in internal fields. + // Then it checks if ngc.t!functionality_override_enabled is enabled and if sys:set GetT is == 2. + // If both conditions are true, it does this following code. Since we currently stub it, it's fine to don't check settings service values. + + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + ulong bufferFilteredPosition = context.Request.RecvListBuff[0].Position; + + string text = ""; + string textFiltered = ""; + + if (bufferSize != 0) + { + if (bufferSize > 1024) + { + textFiltered = new string('*', text.Length); + + context.Memory.Write(bufferFilteredPosition, Encoding.ASCII.GetBytes(textFiltered)); + } + else + { + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + // NOTE: Ngct use the archive 0100000000001034 which contains a words table. This is pushed on Chinese Switchs using Bcat service. + // This call check if the string contains words which are in the table then returns the same string with each matched words replaced by '*'. + // Since we don't want to hide bad words. It's fine to returns the same string. + + textFiltered = text = Encoding.ASCII.GetString(buffer); + + context.Memory.Write(bufferFilteredPosition, buffer); + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceNgct, new { text, textFiltered }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs new file mode 100644 index 00000000..d6a4a29f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Services.Nifm.StaticService; + +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + [Service("nifm:a")] // Max sessions: 2 + [Service("nifm:s")] // Max sessions: 16 + [Service("nifm:u")] // Max sessions: 5 + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + + [CommandCmif(4)] + // CreateGeneralServiceOld() -> object<nn::nifm::detail::IGeneralService> + public ResultCode CreateGeneralServiceOld(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 3.0.0+ + // CreateGeneralService(u64, pid) -> object<nn::nifm::detail::IGeneralService> + public ResultCode CreateGeneralService(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs new file mode 100644 index 00000000..73cadb11 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + enum ResultCode + { + ModuleId = 110, + ErrorCodeShift = 9, + + Success = 0, + + Unknown112 = (112 << ErrorCodeShift) | ModuleId, // IRequest::GetResult + Unknown180 = (180 << ErrorCodeShift) | ModuleId, // IRequest::GetAppletInfo + NoInternetConnection = (300 << ErrorCodeShift) | ModuleId, + ObjectIsNull = (350 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs new file mode 100644 index 00000000..bbb218bb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + static class GeneralServiceManager + { + private static List<GeneralServiceDetail> _generalServices = new List<GeneralServiceDetail>(); + + public static int Count + { + get => _generalServices.Count; + } + + public static void Add(GeneralServiceDetail generalServiceDetail) + { + _generalServices.Add(generalServiceDetail); + } + + public static void Remove(int index) + { + _generalServices.RemoveAt(index); + } + + public static GeneralServiceDetail Get(int clientId) + { + return _generalServices.First(item => item.ClientId == clientId); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs new file mode 100644 index 00000000..3cf55345 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + class GeneralServiceDetail + { + public int ClientId; + public bool IsAnyInternetRequestAccepted; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs new file mode 100644 index 00000000..e9712e92 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs @@ -0,0 +1,203 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types; +using System; +using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService +{ + class IGeneralService : DisposableIpcService + { + private GeneralServiceDetail _generalServiceDetail; + + private IPInterfaceProperties _targetPropertiesCache = null; + private UnicastIPAddressInformation _targetAddressInfoCache = null; + private string _cacheChosenInterface = null; + + public IGeneralService() + { + _generalServiceDetail = new GeneralServiceDetail + { + ClientId = GeneralServiceManager.Count, + IsAnyInternetRequestAccepted = true // NOTE: Why not accept any internet request? + }; + + NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(LocalInterfaceCacheHandler); + + GeneralServiceManager.Add(_generalServiceDetail); + } + + [CommandCmif(1)] + // GetClientId() -> buffer<nn::nifm::ClientId, 0x1a, 4> + public ResultCode GetClientId(ServiceCtx context) + { + ulong position = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(sizeof(int)); + + context.Memory.Write(position, _generalServiceDetail.ClientId); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // CreateRequest(u32 version) -> object<nn::nifm::detail::IRequest> + public ResultCode CreateRequest(ServiceCtx context) + { + uint version = context.RequestData.ReadUInt32(); + + MakeObject(context, new IRequest(context.Device.System, version)); + + // Doesn't occur in our case. + // return ResultCode.ObjectIsNull; + + Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { version }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetCurrentNetworkProfile() -> buffer<nn::nifm::detail::sf::NetworkProfileData, 0x1a, 0x17c> + public ResultCode GetCurrentNetworkProfile(ServiceCtx context) + { + ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position; + + (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (interfaceProperties == null || unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf<NetworkProfileData>()); + + NetworkProfileData networkProfile = new NetworkProfileData + { + Uuid = UInt128Utils.CreateRandom() + }; + + networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress); + networkProfile.IpSettingData.DnsSetting = new DnsSetting(interfaceProperties); + + "RyujinxNetwork"u8.CopyTo(networkProfile.Name.AsSpan()); + + context.Memory.Write(networkProfileDataPosition, networkProfile); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // GetCurrentIpAddress() -> nn::nifm::IpV4Address + public ResultCode GetCurrentIpAddress(ServiceCtx context) + { + (_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + context.ResponseData.WriteStruct(new IpV4Address(unicastAddress.Address)); + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + return ResultCode.Success; + } + + [CommandCmif(15)] + // GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting) + public ResultCode GetCurrentIpConfigInfo(ServiceCtx context) + { + (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (interfaceProperties == null || unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + context.ResponseData.WriteStruct(new IpAddressSetting(interfaceProperties, unicastAddress)); + context.ResponseData.WriteStruct(new DnsSetting(interfaceProperties)); + + return ResultCode.Success; + } + + [CommandCmif(18)] + // GetInternetConnectionStatus() -> nn::nifm::detail::sf::InternetConnectionStatus + public ResultCode GetInternetConnectionStatus(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return ResultCode.NoInternetConnection; + } + + InternetConnectionStatus internetConnectionStatus = new InternetConnectionStatus + { + Type = InternetConnectionType.WiFi, + WifiStrength = 3, + State = InternetConnectionState.Connected, + }; + + context.ResponseData.WriteStruct(internetConnectionStatus); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // IsAnyInternetRequestAccepted(buffer<nn::nifm::ClientId, 0x19, 4>) -> bool + public ResultCode IsAnyInternetRequestAccepted(ServiceCtx context) + { + ulong position = context.Request.PtrBuff[0].Position; + ulong size = context.Request.PtrBuff[0].Size; + + int clientId = context.Memory.Read<int>(position); + + context.ResponseData.Write(GeneralServiceManager.Get(clientId).IsAnyInternetRequestAccepted); + + return ResultCode.Success; + } + + private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return (null, null); + } + + string chosenInterface = context.Device.Configuration.MultiplayerLanInterfaceId; + + if (_targetPropertiesCache == null || _targetAddressInfoCache == null || _cacheChosenInterface != chosenInterface) + { + _cacheChosenInterface = chosenInterface; + + (_targetPropertiesCache, _targetAddressInfoCache) = NetworkHelpers.GetLocalInterface(chosenInterface); + } + + return (_targetPropertiesCache, _targetAddressInfoCache); + } + + private void LocalInterfaceCacheHandler(object sender, EventArgs e) + { + Logger.Info?.Print(LogClass.ServiceNifm, $"NetworkAddress changed, invalidating cached data."); + + _targetPropertiesCache = null; + _targetAddressInfoCache = null; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + NetworkChange.NetworkAddressChanged -= LocalInterfaceCacheHandler; + + GeneralServiceManager.Remove(_generalServiceDetail.ClientId); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs new file mode 100644 index 00000000..87aad30b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs @@ -0,0 +1,142 @@ +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.Nifm.StaticService +{ + class IRequest : IpcService + { + private enum RequestState + { + Error = 1, + OnHold = 2, + Available = 3 + } + + private KEvent _event0; + private KEvent _event1; + + private int _event0Handle; + private int _event1Handle; + + private uint _version; + + public IRequest(Horizon system, uint version) + { + _event0 = new KEvent(system.KernelContext); + _event1 = new KEvent(system.KernelContext); + + _version = version; + } + + [CommandCmif(0)] + // GetRequestState() -> u32 + public ResultCode GetRequestState(ServiceCtx context) + { + RequestState requestState = context.Device.Configuration.EnableInternetAccess + ? RequestState.Available + : RequestState.Error; + + context.ResponseData.Write((int)requestState); + + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return GetResultImpl(); + } + + private ResultCode GetResultImpl() + { + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetSystemEventReadableHandles() -> (handle<copy>, handle<copy>) + public ResultCode GetSystemEventReadableHandles(ServiceCtx context) + { + if (_event0Handle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event0.ReadableEvent, out _event0Handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (_event1Handle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event1.ReadableEvent, out _event1Handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_event0Handle, _event1Handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // Submit() + public ResultCode Submit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetConnectionConfirmationOption(i8) + public ResultCode SetConnectionConfirmationOption(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetAppletInfo(u32) -> (u32, u32, u32, buffer<bytes, 6>) + public ResultCode GetAppletInfo(ServiceCtx context) + { + uint themeColor = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + ResultCode result = GetResultImpl(); + + if (result == ResultCode.Success || (ResultCode)((int)result & 0x3fffff) == ResultCode.Unknown112) + { + return ResultCode.Unknown180; + } + + // Returns appletId, libraryAppletMode, outSize and a buffer. + // Returned applet ids- (0x19, 0xf, 0xe) + // libraryAppletMode seems to be 0 for all applets supported. + + // TODO: check order + context.ResponseData.Write(0xe); // Use error applet as default for now + context.ResponseData.Write(0); // libraryAppletMode + context.ResponseData.Write(0); // outSize + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs new file mode 100644 index 00000000..374558ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 9)] + struct DnsSetting + { + [MarshalAs(UnmanagedType.U1)] + public bool IsDynamicDnsEnabled; + public IpV4Address PrimaryDns; + public IpV4Address SecondaryDns; + + public DnsSetting(IPInterfaceProperties interfaceProperties) + { + IsDynamicDnsEnabled = OperatingSystem.IsWindows() && interfaceProperties.IsDynamicDnsEnabled; + + if (interfaceProperties.DnsAddresses.Count == 0) + { + PrimaryDns = new IpV4Address(); + SecondaryDns = new IpV4Address(); + } + else + { + PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]); + SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs new file mode 100644 index 00000000..dfb8f76c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionState : byte + { + ConnectingType0 = 0, + ConnectingType1 = 1, + ConnectingType2 = 2, + ConnectingType3 = 3, + Connected = 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs new file mode 100644 index 00000000..ff944eca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InternetConnectionStatus + { + public InternetConnectionType Type; + public byte WifiStrength; + public InternetConnectionState State; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs new file mode 100644 index 00000000..af2bcfa1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionType : byte + { + Invalid = 0, + WiFi = 1, + Ethernet = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs new file mode 100644 index 00000000..59c1f6a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xd)] + struct IpAddressSetting + { + [MarshalAs(UnmanagedType.U1)] + public bool IsDhcpEnabled; + public IpV4Address Address; + public IpV4Address IPv4Mask; + public IpV4Address GatewayAddress; + + public IpAddressSetting(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastIPAddressInformation) + { + IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0; + Address = new IpV4Address(unicastIPAddressInformation.Address); + IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask); + GatewayAddress = (interfaceProperties.GatewayAddresses.Count == 0) ? new IpV4Address() : new IpV4Address(interfaceProperties.GatewayAddresses[0].Address); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs new file mode 100644 index 00000000..8ffe824c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xc2)] + struct IpSettingData + { + public IpAddressSetting IpAddressSetting; + public DnsSetting DnsSetting; + public ProxySetting ProxySetting; + public short Mtu; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs new file mode 100644 index 00000000..e5c2f39a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct IpV4Address + { + public uint Address; + + public IpV4Address(IPAddress address) + { + if (address == null) + { + Address = 0; + } + else + { + Address = BitConverter.ToUInt32(address.GetAddressBytes()); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs new file mode 100644 index 00000000..e270c10a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x17C)] + struct NetworkProfileData + { + public IpSettingData IpSettingData; + public UInt128 Uuid; + public Array64<byte> Name; + public Array4<byte> Unknown; + public WirelessSettingData WirelessSettingData; + public byte Padding; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs new file mode 100644 index 00000000..6e534fe1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xaa)] + public struct ProxySetting + { + [MarshalAs(UnmanagedType.I1)] + public bool Enabled; + private byte _padding; + public short Port; + private NameStruct _name; + [MarshalAs(UnmanagedType.I1)] + public bool AutoAuthEnabled; + public Array32<byte> User; + public Array32<byte> Pass; + private byte _padding2; + + [StructLayout(LayoutKind.Sequential, Size = 0x64)] + private struct NameStruct { } + + public Span<byte> Name => SpanHelpers.AsSpan<NameStruct, byte>(ref _name); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs new file mode 100644 index 00000000..8aa122c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x65)] + struct WirelessSettingData + { + public byte SsidLength; + public Array32<byte> Ssid; + public Array3<byte> Unknown; + public Array64<byte> Passphrase1; + public byte Passphrase2; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs b/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs new file mode 100644 index 00000000..ad79ca0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim")] + class INetworkInstallManager : IpcService + { + public INetworkInstallManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs new file mode 100644 index 00000000..ab17871f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer; + +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface +{ + class IShopServiceAccessServer : IpcService + { + public IShopServiceAccessServer() { } + + [CommandCmif(0)] + // CreateAccessorInterface(u8) -> object<nn::ec::IShopServiceAccessor> + public ResultCode CreateAccessorInterface(ServiceCtx context) + { + MakeObject(context, new IShopServiceAccessor(context.Device.System)); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs new file mode 100644 index 00000000..950004fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs @@ -0,0 +1,44 @@ +using LibHac.Ncm; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface; + +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:eca")] // 5.0.0+ + class IShopServiceAccessServerInterface : IpcService + { + public IShopServiceAccessServerInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateServerInterface(pid, handle<unknown>, u64) -> object<nn::ec::IShopServiceAccessServer> + public ResultCode CreateServerInterface(ServiceCtx context) + { + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + MakeObject(context, new IShopServiceAccessServer()); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + + [CommandCmif(4)] // 10.0.0+ + // IsLargeResourceAvailable(pid) -> b8 + public ResultCode IsLargeResourceAvailable(ServiceCtx context) + { + // TODO: Service calls arp:r GetApplicationInstanceId (10.0.0+) then if it fails it calls arp:r GetMicroApplicationInstanceId (10.0.0+) + // then if it fails it returns the arp:r result code. + + // NOTE: Firmare 10.0.0+ don't use the Pid here anymore, but the returned InstanceId. We don't support that for now so we can just use the Pid instead. + StorageId baseStorageId = (StorageId)ApplicationLaunchProperty.GetByPid(context).BaseGameStorageId; + + // NOTE: Service returns ResultCode.InvalidArgument if baseStorageId is null, doesn't occur in our case. + + context.ResponseData.Write(baseStorageId == StorageId.Host); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs new file mode 100644 index 00000000..bf201b98 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:ecas")] // 7.0.0+ + class IShopServiceAccessSystemInterface : IpcService + { + public IShopServiceAccessSystemInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs new file mode 100644 index 00000000..3c0136fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs @@ -0,0 +1,42 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer +{ + class IShopServiceAccessor : IpcService + { + private readonly KEvent _event; + + private int _eventHandle; + + public IShopServiceAccessor(Horizon system) + { + _event = new KEvent(system.KernelContext); + } + + [CommandCmif(0)] + // CreateAsyncInterface(u64) -> (handle<copy>, object<nn::ec::IShopServiceAsync>) + public ResultCode CreateAsyncInterface(ServiceCtx context) + { + MakeObject(context, new IShopServiceAsync()); + + if (_eventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out _eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_eventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs new file mode 100644 index 00000000..81d892c5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor +{ + class IShopServiceAsync : IpcService + { + public IShopServiceAsync() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs new file mode 100644 index 00000000..2420615a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:shp")] + class IShopServiceManager : IpcService + { + public IShopServiceManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs new file mode 100644 index 00000000..4a63615b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService; + +namespace Ryujinx.HLE.HOS.Services.Nim.Ntc +{ + [Service("ntc")] + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenEnsureNetworkClockAvailabilityService(u64) -> object<nn::ntc::detail::service::IEnsureNetworkClockAvailabilityService> + public ResultCode CreateAsyncInterface(ServiceCtx context) + { + ulong unknown = context.RequestData.ReadUInt64(); + + MakeObject(context, new IEnsureNetworkClockAvailabilityService(context)); + + Logger.Stub?.PrintStub(LogClass.ServiceNtc, new { unknown }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs new file mode 100644 index 00000000..82d0b5a8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs @@ -0,0 +1,77 @@ +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.Nim.Ntc.StaticService +{ + class IEnsureNetworkClockAvailabilityService : IpcService + { + private KEvent _finishNotificationEvent; + private ResultCode _taskResultCode; + + public IEnsureNetworkClockAvailabilityService(ServiceCtx context) + { + _finishNotificationEvent = new KEvent(context.Device.System.KernelContext); + _taskResultCode = ResultCode.Success; + + // NOTE: The service starts a thread that polls Nintendo NTP server and syncs the time with it. + // Additionnally it gets and uses some settings too: + // autonomic_correction_interval_seconds, autonomic_correction_failed_retry_interval_seconds, + // autonomic_correction_immediate_try_count_max, autonomic_correction_immediate_try_interval_milliseconds + } + + [CommandCmif(0)] + // StartTask() + public ResultCode StartTask(ServiceCtx context) + { + if (!context.Device.Configuration.EnableInternetAccess) + { + return (ResultCode)Time.ResultCode.NetworkTimeNotAvailable; + } + + // NOTE: Since we don't support the Nintendo NTP server, we can signal the event now to confirm the update task is done. + _finishNotificationEvent.ReadableEvent.Signal(); + + Logger.Stub?.PrintStub(LogClass.ServiceNtc); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetFinishNotificationEvent() -> handle<copy> + public ResultCode GetFinishNotificationEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_finishNotificationEvent.ReadableEvent, out int finishNotificationEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(finishNotificationEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + return _taskResultCode; + } + + [CommandCmif(3)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + // NOTE: The update task should be canceled here. + _finishNotificationEvent.ReadableEvent.Signal(); + + _taskResultCode = (ResultCode)Time.ResultCode.NetworkTimeTaskCanceled; + + Logger.Stub?.PrintStub(LogClass.ServiceNtc); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs new file mode 100644 index 00000000..166e39a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + enum ResultCode + { + ModuleId = 137, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (90 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs new file mode 100644 index 00000000..c4a35b29 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:a")] // 9.0.0+ + class INotificationServicesForApplication : IpcService + { + public INotificationServicesForApplication(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs new file mode 100644 index 00000000..0939dff6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:s")] // 9.0.0+ + class INotificationServicesForSystem : IpcService + { + public INotificationServicesForSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs new file mode 100644 index 00000000..fd8ccfb5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:s")] + class INpnsSystem : IpcService + { + public INpnsSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs new file mode 100644 index 00000000..68e76938 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:u")] + class INpnsUser : IpcService + { + public INpnsUser(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs new file mode 100644 index 00000000..b4b5bb1f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs @@ -0,0 +1,346 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + [Service("aoc:u")] + class IAddOnContentManager : IpcService + { + private readonly KEvent _addOnContentListChangedEvent; + private int _addOnContentListChangedEventHandle; + + private ulong _addOnContentBaseId; + + private List<ulong> _mountedAocTitleIds = new List<ulong>(); + + public IAddOnContentManager(ServiceCtx context) + { + _addOnContentListChangedEvent = new KEvent(context.Device.System.KernelContext); + } + + [CommandCmif(0)] // 1.0.0-6.2.0 + // CountAddOnContentByApplicationId(u64 title_id) -> u32 + public ResultCode CountAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return CountAddOnContentImpl(context, titleId); + } + + [CommandCmif(1)] // 1.0.0-6.2.0 + // ListAddOnContentByApplicationId(u64 title_id, u32 start_index, u32 buffer_size) -> (u32 count, buffer<u32>) + public ResultCode ListAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return ListAddContentImpl(context, titleId); + } + + [CommandCmif(2)] + // CountAddOnContent(pid) -> u32 + public ResultCode CountAddOnContent(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(3)] + // ListAddOnContent(u32 start_index, u32 buffer_size, pid) -> (u32 count, buffer<u32>) + public ResultCode ListAddOnContent(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(4)] // 1.0.0-6.2.0 + // GetAddOnContentBaseIdByApplicationId(u64 title_id) -> u64 + public ResultCode GetAddOnContentBaseIdByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return GetAddOnContentBaseIdImpl(context, titleId); + } + + [CommandCmif(5)] + // GetAddOnContentBaseId(pid) -> u64 + public ResultCode GetAddOnContentBaseId(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(6)] // 1.0.0-6.2.0 + // PrepareAddOnContentByApplicationId(u64 title_id, u32 index) + public ResultCode PrepareAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return PrepareAddOnContentImpl(context, titleId); + } + + [CommandCmif(7)] + // PrepareAddOnContent(u32 index, pid) + public ResultCode PrepareAddOnContent(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(8)] // 4.0.0+ + // GetAddOnContentListChangedEvent() -> handle<copy> + public ResultCode GetAddOnContentListChangedEvent(ServiceCtx context) + { + return GetAddOnContentListChangedEventImpl(context); + } + + [CommandCmif(9)] // 10.0.0+ + // GetAddOnContentLostErrorCode() -> u64 + public ResultCode GetAddOnContentLostErrorCode(ServiceCtx context) + { + // NOTE: 0x7D0A4 -> 2164-1000 + context.ResponseData.Write(GetAddOnContentLostErrorCodeImpl(0x7D0A4)); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 11.0.0+ + // GetAddOnContentListChangedEventWithProcessId(pid) -> handle<copy> + public ResultCode GetAddOnContentListChangedEventWithProcessId(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + // TODO: Found where stored value is used. + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + return GetAddOnContentListChangedEventImpl(context); + } + + [CommandCmif(11)] // 13.0.0+ + // NotifyMountAddOnContent(pid, u64 title_id) + public ResultCode NotifyMountAddOnContent(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + ulong aocTitleId = context.RequestData.ReadUInt64(); + + if (_mountedAocTitleIds.Count <= 0x7F) + { + _mountedAocTitleIds.Add(aocTitleId); + } + + return ResultCode.Success; + } + + [CommandCmif(12)] // 13.0.0+ + // NotifyUnmountAddOnContent(pid, u64 title_id) + public ResultCode NotifyUnmountAddOnContent(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + ulong aocTitleId = context.RequestData.ReadUInt64(); + + _mountedAocTitleIds.Remove(aocTitleId); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 13.0.0+ + // CheckAddOnContentMountStatus(pid) + public ResultCode CheckAddOnContentMountStatus(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + // Then it does some internal checks and returns InvalidBufferSize if they fail. + + Logger.Stub?.PrintStub(LogClass.ServiceNs); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 7.0.0+ + // CreateEcPurchasedEventManager() -> object<nn::ec::IPurchaseEventManager> + public ResultCode CreateEcPurchasedEventManager(ServiceCtx context) + { + MakeObject(context, new IPurchaseEventManager(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(101)] // 9.0.0+ + // CreatePermanentEcPurchasedEventManager() -> object<nn::ec::IPurchaseEventManager> + public ResultCode CreatePermanentEcPurchasedEventManager(ServiceCtx context) + { + // NOTE: Service call arp:r to get the TitleId, do some extra checks and pass it to returned interface. + + MakeObject(context, new IPurchaseEventManager(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(110)] // 12.0.0+ + // CreateContentsServiceManager() -> object<nn::ec::IContentsServiceManager> + public ResultCode CreateContentsServiceManager(ServiceCtx context) + { + MakeObject(context, new IContentsServiceManager()); + + return ResultCode.Success; + } + + private ResultCode CountAddOnContentImpl(ServiceCtx context, ulong titleId) + { + // NOTE: Service call sys:set GetQuestFlag and store it internally. + // If QuestFlag is true, counts some extra titles. + + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, titleId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + // TODO: This should use _addOnContentBaseId; + uint aocCount = (uint)context.Device.System.ContentManager.GetAocCount(); + + context.ResponseData.Write(aocCount); + + return ResultCode.Success; + } + + private ResultCode ListAddContentImpl(ServiceCtx context, ulong titleId) + { + // NOTE: Service call sys:set GetQuestFlag and store it internally. + // If QuestFlag is true, counts some extra titles. + + uint startIndex = context.RequestData.ReadUInt32(); + uint indexNumber = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + // TODO: This should use _addOnContentBaseId; + uint aocTotalCount = (uint)context.Device.System.ContentManager.GetAocCount(); + + if (indexNumber > bufferSize / sizeof(uint)) + { + return ResultCode.InvalidBufferSize; + } + + if (aocTotalCount <= startIndex) + { + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + IList<ulong> aocTitleIds = context.Device.System.ContentManager.GetAocTitleIds(); + + GetAddOnContentBaseIdFromTitleId(context, titleId); + + uint indexCounter = 0; + + for (int i = 0; i < indexNumber; i++) + { + if (i + (int)startIndex < aocTitleIds.Count) + { + context.Memory.Write(bufferPosition + (ulong)i * sizeof(uint), (uint)(aocTitleIds[i + (int)startIndex] - _addOnContentBaseId)); + + indexCounter++; + } + } + + context.ResponseData.Write(indexCounter); + + return ResultCode.Success; + } + + private ResultCode GetAddOnContentBaseIdImpl(ServiceCtx context, ulong titleId) + { + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, titleId); + + context.ResponseData.Write(_addOnContentBaseId); + + return resultCode; + } + + private ResultCode GetAddOnContentBaseIdFromTitleId(ServiceCtx context, ulong titleId) + { + // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, + // If the call fails, it returns ResultCode.InvalidPid. + + _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId; + + if (_addOnContentBaseId == 0) + { + _addOnContentBaseId = titleId + 0x1000; + } + + return ResultCode.Success; + } + + private ResultCode PrepareAddOnContentImpl(ServiceCtx context, ulong titleId) + { + uint index = context.RequestData.ReadUInt32(); + + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + // TODO: Service calls ns:am RegisterContentsExternalKey?, GetOwnedApplicationContentMetaStatus? etc... + // Ideally, this should probably initialize the AocData values for the specified index + + Logger.Stub?.PrintStub(LogClass.ServiceNs, new { index }); + + return ResultCode.Success; + } + + private ResultCode GetAddOnContentListChangedEventImpl(ServiceCtx context) + { + if (_addOnContentListChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_addOnContentListChangedEvent.ReadableEvent, out _addOnContentListChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_addOnContentListChangedEventHandle); + + return ResultCode.Success; + } + + private static ulong GetAddOnContentLostErrorCodeImpl(int errorCode) + { + return ((ulong)errorCode & 0x1FF | ((((ulong)errorCode >> 9) & 0x1FFF) << 32)) + 2000; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs new file mode 100644 index 00000000..cb8903d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + class IContentsServiceManager : IpcService + { + public IContentsServiceManager() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs new file mode 100644 index 00000000..1673fafc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs @@ -0,0 +1,68 @@ +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.Ns.Aoc +{ + class IPurchaseEventManager : IpcService + { + private readonly KEvent _purchasedEvent; + + public IPurchaseEventManager(Horizon system) + { + _purchasedEvent = new KEvent(system.KernelContext); + } + + [CommandCmif(0)] + // SetDefaultDeliveryTarget(pid, buffer<bytes, 5> unknown) + public ResultCode SetDefaultDeliveryTarget(ServiceCtx context) + { + ulong inBufferPosition = context.Request.SendBuff[0].Position; + ulong inBufferSize = context.Request.SendBuff[0].Size; + byte[] buffer = new byte[inBufferSize]; + + context.Memory.Read(inBufferPosition, buffer); + + // NOTE: Service uses the pid to call arp:r GetApplicationLaunchProperty and store it in internal field. + // Then it seems to use the buffer content and compare it with a stored linked instrusive list. + // Since we don't support purchase from eShop, we can stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceNs); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetPurchasedEventReadableHandle() -> handle<copy, event> + public ResultCode GetPurchasedEventReadableHandle(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_purchasedEvent.ReadableEvent, out int purchasedEventReadableHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(purchasedEventReadableHandle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // PopPurchasedProductInfo(nn::ec::detail::PurchasedProductInfo) + public ResultCode PopPurchasedProductInfo(ServiceCtx context) + { + byte[] purchasedProductInfo = new byte[0x80]; + + context.ResponseData.Write(purchasedProductInfo); + + // NOTE: Service finds info using internal array then convert it into nn::ec::detail::PurchasedProductInfo. + // Returns 0x320A4 if the internal array size is null. + // Since we don't support purchase from eShop, we can stub it. + + Logger.Debug?.PrintStub(LogClass.ServiceNs); // NOTE: Uses Debug to avoid spamming. + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs new file mode 100644 index 00000000..7602ecb3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + enum ResultCode + { + ModuleId = 166, + ErrorCodeShift = 9, + + Success = 0, + + InvalidBufferSize = (200 << ErrorCodeShift) | ModuleId, + InvalidPid = (300 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs new file mode 100644 index 00000000..06e911f8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs @@ -0,0 +1,28 @@ +using LibHac.Ns; +using Ryujinx.Common.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am")] + class IApplicationManagerInterface : IpcService + { + public IApplicationManagerInterface(ServiceCtx context) { } + + [CommandCmif(400)] + // GetApplicationControlData(u8, u64) -> (unknown<4>, buffer<unknown, 6>) + public ResultCode GetApplicationControlData(ServiceCtx context) + { + byte source = (byte)context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); + + ulong position = context.Request.ReceiveBuff[0].Position; + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs new file mode 100644 index 00000000..c74ebd69 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:dev")] + class IDevelopInterface : IpcService + { + public IDevelopInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs new file mode 100644 index 00000000..aa37a1e7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs @@ -0,0 +1,26 @@ +using LibHac.Common; +using LibHac.Ns; + +namespace Ryujinx.HLE.HOS.Services.Ns +{ + class IReadOnlyApplicationControlDataInterface : IpcService + { + public IReadOnlyApplicationControlDataInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetApplicationControlData(u8, u64) -> (unknown<4>, buffer<unknown, 6>) + public ResultCode GetApplicationControlData(ServiceCtx context) + { + byte source = (byte)context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); + + ulong position = context.Request.ReceiveBuff[0].Position; + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs new file mode 100644 index 00000000..886bffdd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am2")] + [Service("ns:ec")] + [Service("ns:rid")] + [Service("ns:rt")] + [Service("ns:web")] + class IServiceGetterInterface : IpcService + { + public IServiceGetterInterface(ServiceCtx context) { } + + [CommandCmif(7996)] + // GetApplicationManagerInterface() -> object<nn::ns::detail::IApplicationManagerInterface> + public ResultCode GetApplicationManagerInterface(ServiceCtx context) + { + MakeObject(context, new IApplicationManagerInterface(context)); + + return ResultCode.Success; + } + + [CommandCmif(7989)] + // GetReadOnlyApplicationControlDataInterface() -> object<nn::ns::detail::IReadOnlyApplicationControlDataInterface> + public ResultCode GetReadOnlyApplicationControlDataInterface(ServiceCtx context) + { + MakeObject(context, new IReadOnlyApplicationControlDataInterface(context)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs new file mode 100644 index 00000000..84ed3d0f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:su")] + class ISystemUpdateInterface : IpcService + { + public ISystemUpdateInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs new file mode 100644 index 00000000..0b640992 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:vm")] + class IVulnerabilityManagerInterface : IpcService + { + public IVulnerabilityManagerInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs new file mode 100644 index 00000000..bb609fa4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Host1x; +using Ryujinx.Graphics.Nvdec; +using Ryujinx.Graphics.Vic; +using System; +using GpuContext = Ryujinx.Graphics.Gpu.GpuContext; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class Host1xContext : IDisposable + { + public MemoryManager Smmu { get; } + public NvMemoryAllocator MemoryAllocator { get; } + public Host1xDevice Host1x { get;} + + public Host1xContext(GpuContext gpu, ulong pid) + { + MemoryAllocator = new NvMemoryAllocator(); + Host1x = new Host1xDevice(gpu.Synchronization); + Smmu = gpu.CreateMemoryManager(pid); + var nvdec = new NvdecDevice(Smmu); + var vic = new VicDevice(Smmu); + Host1x.RegisterDevice(ClassId.Nvdec, nvdec); + Host1x.RegisterDevice(ClassId.Vic, vic); + } + + public void Dispose() + { + Host1x.Dispose(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs new file mode 100644 index 00000000..dffe8783 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrvdbg")] + class INvDrvDebugFSServices : IpcService + { + public INvDrvDebugFSServices(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs new file mode 100644 index 00000000..1d075d43 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -0,0 +1,598 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrv")] + [Service("nvdrv:a")] + [Service("nvdrv:s")] + [Service("nvdrv:t")] + class INvDrvServices : IpcService + { + private static readonly List<string> _deviceFileDebugRegistry = new List<string>() + { + "/dev/nvhost-dbg-gpu", + "/dev/nvhost-prof-gpu" + }; + + private static readonly Dictionary<string, Type> _deviceFileRegistry = new Dictionary<string, Type>() + { + { "/dev/nvmap", typeof(NvMapDeviceFile) }, + { "/dev/nvhost-ctrl", typeof(NvHostCtrlDeviceFile) }, + { "/dev/nvhost-ctrl-gpu", typeof(NvHostCtrlGpuDeviceFile) }, + { "/dev/nvhost-as-gpu", typeof(NvHostAsGpuDeviceFile) }, + { "/dev/nvhost-gpu", typeof(NvHostGpuDeviceFile) }, + //{ "/dev/nvhost-msenc", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-nvdec", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-nvjpg", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-vic", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-display", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) }, + { "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) }, + }; + + public static IdDictionary DeviceFileIdRegistry = new IdDictionary(); + + private IVirtualMemoryManager _clientMemory; + private ulong _owner; + + private bool _transferMemInitialized = false; + + // TODO: This should call set:sys::GetDebugModeFlag + private bool _debugModeEnabled = false; + + public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) + { + _owner = 0; + } + + private NvResult Open(ServiceCtx context, string path, out int fd) + { + fd = -1; + + if (!_debugModeEnabled && _deviceFileDebugRegistry.Contains(path)) + { + return NvResult.NotSupported; + } + + if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass)) + { + ConstructorInfo constructor = deviceFileClass.GetConstructor(new Type[] { typeof(ServiceCtx), typeof(IVirtualMemoryManager), typeof(ulong) }); + + NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context, _clientMemory, _owner }); + + deviceFile.Path = path; + + fd = DeviceFileIdRegistry.Add(deviceFile); + + return NvResult.Success; + } + + Logger.Warning?.Print(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!"); + + return NvResult.FileOperationFailed; + } + + private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span<byte> arguments) + { + (ulong inputDataPosition, ulong inputDataSize) = context.Request.GetBufferType0x21(0); + (ulong outputDataPosition, ulong outputDataSize) = context.Request.GetBufferType0x22(0); + + NvIoctl.Direction ioctlDirection = ioctlCommand.DirectionValue; + uint ioctlSize = ioctlCommand.Size; + + bool isRead = (ioctlDirection & NvIoctl.Direction.Read) != 0; + bool isWrite = (ioctlDirection & NvIoctl.Direction.Write) != 0; + + if ((isWrite && ioctlSize > outputDataSize) || (isRead && ioctlSize > inputDataSize)) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + if (isRead && isWrite) + { + if (outputDataSize < inputDataSize) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + byte[] outputData = new byte[outputDataSize]; + + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length); + + arguments = new Span<byte>(outputData); + } + else if (isWrite) + { + byte[] outputData = new byte[outputDataSize]; + + arguments = new Span<byte>(outputData); + } + else + { + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + arguments = new Span<byte>(temp); + } + + return NvResult.Success; + } + + private NvResult GetDeviceFileFromFd(int fd, out NvDeviceFile deviceFile) + { + deviceFile = null; + + if (fd < 0) + { + return NvResult.InvalidParameter; + } + + deviceFile = DeviceFileIdRegistry.GetData<NvDeviceFile>(fd); + + if (deviceFile == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid file descriptor {fd}"); + + return NvResult.NotImplemented; + } + + if (deviceFile.Owner != _owner) + { + return NvResult.AccessDenied; + } + + return NvResult.Success; + } + + private NvResult EnsureInitialized() + { + if (_owner == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, "INvDrvServices is not initialized!"); + + return NvResult.NotInitialized; + } + + return NvResult.Success; + } + + private static NvResult ConvertInternalErrorCode(NvInternalResult errorCode) + { + switch (errorCode) + { + case NvInternalResult.Success: + return NvResult.Success; + case NvInternalResult.Unknown0x72: + return NvResult.AlreadyAllocated; + case NvInternalResult.TimedOut: + case NvInternalResult.TryAgain: + case NvInternalResult.Interrupted: + return NvResult.Timeout; + case NvInternalResult.InvalidAddress: + return NvResult.InvalidAddress; + case NvInternalResult.NotSupported: + case NvInternalResult.Unknown0x18: + return NvResult.NotSupported; + case NvInternalResult.InvalidState: + return NvResult.InvalidState; + case NvInternalResult.ReadOnlyAttribute: + return NvResult.ReadOnlyAttribute; + case NvInternalResult.NoSpaceLeft: + case NvInternalResult.FileTooBig: + return NvResult.InvalidSize; + case NvInternalResult.FileTableOverflow: + case NvInternalResult.BadFileNumber: + return NvResult.FileOperationFailed; + case NvInternalResult.InvalidInput: + return NvResult.InvalidValue; + case NvInternalResult.NotADirectory: + return NvResult.DirectoryOperationFailed; + case NvInternalResult.Busy: + return NvResult.Busy; + case NvInternalResult.BadAddress: + return NvResult.InvalidAddress; + case NvInternalResult.AccessDenied: + case NvInternalResult.OperationNotPermitted: + return NvResult.AccessDenied; + case NvInternalResult.OutOfMemory: + return NvResult.InsufficientMemory; + case NvInternalResult.DeviceNotFound: + return NvResult.ModuleNotPresent; + case NvInternalResult.IoError: + return NvResult.ResourceError; + default: + return NvResult.IoctlFailed; + } + } + + [CommandCmif(0)] + // Open(buffer<bytes, 5> path) -> (s32 fd, u32 error_code) + public ResultCode Open(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + int fd = -1; + + if (errorCode == NvResult.Success) + { + ulong pathPtr = context.Request.SendBuff[0].Position; + ulong pathSize = context.Request.SendBuff[0].Size; + + string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr, (long)pathSize); + + errorCode = Open(context, path, out fd); + } + + context.ResponseData.Write(fd); + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Ioctl(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args) + public ResultCode Ioctl(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl(ioctlCommand, arguments); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Close(s32 fd) -> u32 error_code + public ResultCode Close(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + deviceFile.Close(); + + DeviceFileIdRegistry.Delete(fd); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Initialize(u32 transfer_memory_size, handle<copy, process> current_process, handle<copy, transfer_memory> transfer_memory) -> u32 error_code + public ResultCode Initialize(ServiceCtx context) + { + long transferMemSize = context.RequestData.ReadInt64(); + int transferMemHandle = context.Request.HandleDesc.ToCopy[1]; + + // TODO: When transfer memory will be implemented, this could be removed. + _transferMemInitialized = true; + + int clientHandle = context.Request.HandleDesc.ToCopy[0]; + + _clientMemory = context.Process.HandleTable.GetKProcess(clientHandle).CpuMemory; + + context.Device.System.KernelContext.Syscall.GetProcessId(out _owner, clientHandle); + + context.ResponseData.Write((uint)NvResult.Success); + + // Close the process and transfer memory handles immediately as we don't use them. + context.Device.System.KernelContext.Syscall.CloseHandle(clientHandle); + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemHandle); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // QueryEvent(s32 fd, u32 event_id) -> (u32, handle<copy, event>) + public ResultCode QueryEvent(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint eventId = context.RequestData.ReadUInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.QueryEvent(out int eventHandle, eventId); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvQueryEventNotImplementedException(context, deviceFile, eventId); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(eventHandle); + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // MapSharedMemory(s32 fd, u32 argument, handle<copy, shared_memory>) -> u32 error_code + public ResultCode MapSharedMemory(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint argument = context.RequestData.ReadUInt32(); + int sharedMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemoryHandle, argument)); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetStatus() -> (unknown<0x20>, u32 error_code) + public ResultCode GetStatus(ServiceCtx context) + { + // TODO: When transfer memory will be implemented, check if it's mapped instead. + if (_transferMemInitialized) + { + // TODO: Populate values when more RE will be done. + NvStatus nvStatus = new NvStatus + { + MemoryValue1 = 0, // GetMemStats(transfer_memory + 0x60, 3) + MemoryValue2 = 0, // GetMemStats(transfer_memory + 0x60, 5) + MemoryValue3 = 0, // transfer_memory + 0x78 + MemoryValue4 = 0 // transfer_memory + 0x80 + }; + + context.ResponseData.WriteStruct(nvStatus); + context.ResponseData.Write((uint)NvResult.Success); + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + } + else + { + context.ResponseData.Write((uint)NvResult.NotInitialized); + } + + return ResultCode.Success; + } + + [CommandCmif(7)] + // ForceSetClientPid(u64) -> u32 error_code + public ResultCode ForceSetClientPid(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(8)] + // SetClientPID(u64, pid) -> u32 error_code + public ResultCode SetClientPid(ServiceCtx context) + { + long pid = context.RequestData.ReadInt64(); + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // DumpGraphicsMemoryInfo() + public ResultCode DumpGraphicsMemoryInfo(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 3.0.0+ + // InitializeDevtools(u32, handle<copy>) -> u32 error_code; + public ResultCode InitializeDevtools(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] // 3.0.0+ + // Ioctl2(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args, buffer<bytes, 0x21> inline_in_buffer) -> (u32 error_code, buffer<bytes, 0x22> out_args) + public ResultCode Ioctl2(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + (ulong inlineInBufferPosition, ulong inlineInBufferSize) = context.Request.GetBufferType0x21(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + byte[] temp = new byte[inlineInBufferSize]; + + context.Memory.Read(inlineInBufferPosition, temp); + + Span<byte> inlineInBuffer = new Span<byte>(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 3.0.0+ + // Ioctl3(s32 fd, u32 ioctl_cmd, buffer<bytes, 0x21> in_args) -> (u32 error_code, buffer<bytes, 0x22> out_args, buffer<bytes, 0x22> inline_out_buffer) + public ResultCode Ioctl3(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct<NvIoctl>(); + + (ulong inlineOutBufferPosition, ulong inlineOutBufferSize) = context.Request.GetBufferType0x22(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span<byte> arguments); + + byte[] temp = new byte[inlineOutBufferSize]; + + context.Memory.Read(inlineOutBufferPosition, temp); + + Span<byte> inlineOutBuffer = new Span<byte>(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(13)] // 3.0.0+ + // FinishInitialize(unknown<8>) + public ResultCode FinishInitialize(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + public static void Destroy() + { + NvHostChannelDeviceFile.Destroy(); + + foreach (object entry in DeviceFileIdRegistry.Values) + { + NvDeviceFile deviceFile = (NvDeviceFile)entry; + + deviceFile.Close(); + } + + DeviceFileIdRegistry.Clear(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs new file mode 100644 index 00000000..7bf99ed1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:c")] + class INvGemControl : IpcService + { + public INvGemControl(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs new file mode 100644 index 00000000..ff3774da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:cd")] + class INvGemCoreDump : IpcService + { + public INvGemCoreDump(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs new file mode 100644 index 00000000..9568fc84 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs @@ -0,0 +1,94 @@ +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + abstract class NvDeviceFile + { + public readonly ServiceCtx Context; + public readonly ulong Owner; + + public string Path; + + public NvDeviceFile(ServiceCtx context, ulong owner) + { + Context = context; + Owner = owner; + } + + public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = 0; + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult MapSharedMemory(int sharedMemoryHandle, uint argument) + { + // Close shared memory immediately as we don't use it. + Context.Device.System.KernelContext.Syscall.CloseHandle(sharedMemoryHandle); + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + return NvInternalResult.NotImplemented; + } + + protected delegate NvInternalResult IoctlProcessor<T>(ref T arguments); + protected delegate NvInternalResult IoctlProcessorSpan<T>(Span<T> arguments); + protected delegate NvInternalResult IoctlProcessorInline<T, T1>(ref T arguments, ref T1 inlineData); + protected delegate NvInternalResult IoctlProcessorInlineSpan<T, T1>(ref T arguments, Span<T1> inlineData); + + private static NvInternalResult PrintResult(MethodInfo info, NvInternalResult result) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"{info.Name} returned result {result}"); + + return result; + } + + protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessor<T> callback, Span<byte> arguments) where T : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0])); + } + + protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInline<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + Debug.Assert(inlineBuffer.Length == Unsafe.SizeOf<T1>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], ref MemoryMarshal.Cast<byte, T1>(inlineBuffer)[0])); + } + + protected static NvInternalResult CallIoctlMethod<T>(IoctlProcessorSpan<T> callback, Span<byte> arguments) where T : struct + { + return PrintResult(callback.Method, callback(MemoryMarshal.Cast<byte, T>(arguments))); + } + + protected static NvInternalResult CallIoctlMethod<T, T1>(IoctlProcessorInlineSpan<T, T1> callback, Span<byte> arguments, Span<byte> inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf<T>()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast<byte, T>(arguments)[0], MemoryMarshal.Cast<byte, T1>(inlineBuffer))); + } + + public abstract void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs new file mode 100644 index 00000000..0e0fe7f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -0,0 +1,401 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu +{ + class NvHostAsGpuDeviceFile : NvDeviceFile + { + private const uint SmallPageSize = 0x1000; + private const uint BigPageSize = 0x10000; + + private static readonly uint[] _pageSizes = new uint[] { SmallPageSize, BigPageSize }; + + private const ulong SmallRegionLimit = 0x400000000UL; // 16 GiB + private const ulong DefaultUserSize = 1UL << 37; + + private readonly struct VmRegion + { + public ulong Start { get; } + public ulong Limit { get; } + + public VmRegion(ulong start, ulong limit) + { + Start = start; + Limit = limit; + } + } + + private static readonly VmRegion[] _vmRegions = new VmRegion[] + { + new VmRegion((ulong)BigPageSize << 16, SmallRegionLimit), + new VmRegion(SmallRegionLimit, DefaultUserSize) + }; + + private readonly AddressSpaceContext _asContext; + private readonly NvMemoryAllocator _memoryAllocator; + + public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _asContext = new AddressSpaceContext(context.Device.Gpu.CreateMemoryManager(owner)); + _memoryAllocator = new NvMemoryAllocator(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<BindChannelArguments>(BindChannel, arguments); + break; + case 0x02: + result = CallIoctlMethod<AllocSpaceArguments>(AllocSpace, arguments); + break; + case 0x03: + result = CallIoctlMethod<FreeSpaceArguments>(FreeSpace, arguments); + break; + case 0x05: + result = CallIoctlMethod<UnmapBufferArguments>(UnmapBuffer, arguments); + break; + case 0x06: + result = CallIoctlMethod<MapBufferExArguments>(MapBufferEx, arguments); + break; + case 0x08: + result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments); + break; + case 0x09: + result = CallIoctlMethod<InitializeExArguments>(InitializeEx, arguments); + break; + case 0x14: + result = CallIoctlMethod<RemapArguments>(Remap, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x08: + // This is the same as the one in ioctl as inlineOutBuffer is empty. + result = CallIoctlMethod<GetVaRegionsArguments>(GetVaRegions, arguments); + break; + } + } + + return result; + } + + private NvInternalResult BindChannel(ref BindChannelArguments arguments) + { + var channelDeviceFile = INvDrvServices.DeviceFileIdRegistry.GetData<NvHostChannelDeviceFile>(arguments.Fd); + if (channelDeviceFile == null) + { + // TODO: Return invalid Fd error. + } + + channelDeviceFile.Channel.BindMemory(_asContext.Gmm); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0) + { + bool regionInUse = _memoryAllocator.IsRegionInUse(arguments.Offset, size, out ulong freeAddressStartPosition); + ulong address; + + if (!regionInUse) + { + _memoryAllocator.AllocateRange(arguments.Offset, size, freeAddressStartPosition); + address = freeAddressStartPosition; + } + else + { + address = NvMemoryAllocator.PteUnmapped; + } + + arguments.Offset = address; + } + else + { + ulong address = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, arguments.Offset); + if (address != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(address, size, freeAddressStartPosition); + } + + arguments.Offset = address; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!"); + + result = NvInternalResult.OutOfMemory; + } + else + { + _asContext.AddReservation(arguments.Offset, size); + } + } + + return result; + } + + private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + if (_asContext.RemoveReservation(arguments.Offset)) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, + $"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments) + { + lock (_asContext) + { + if (_asContext.RemoveMap(arguments.Offset, out ulong size)) + { + if (size != 0) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!"); + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments) + { + const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!"; + + ulong physicalAddress; + + if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0) + { + lock (_asContext) + { + if (_asContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress)) + { + ulong virtualAddress = arguments.Offset + arguments.BufferOffset; + + physicalAddress += arguments.BufferOffset; + _asContext.Gmm.Map(physicalAddress, virtualAddress, arguments.MappingSize, (PteKind)arguments.Kind); + + return NvInternalResult.Success; + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!"); + + return NvInternalResult.InvalidInput; + } + } + } + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + ulong pageSize = (ulong)arguments.PageSize; + + if (pageSize == 0) + { + pageSize = (ulong)map.Align; + } + + physicalAddress = map.Address + arguments.BufferOffset; + + ulong size = arguments.MappingSize; + + if (size == 0) + { + size = (uint)map.Size; + } + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0; + + if (!virtualAddressAllocated) + { + if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize)) + { + _asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind); + } + else + { + string message = string.Format(MapErrorMsg, arguments.Offset, size, pageSize); + + Logger.Warning?.Print(LogClass.ServiceNv, message); + + result = NvInternalResult.InvalidInput; + } + } + else + { + ulong va = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, pageSize); + if (va != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(va, size, freeAddressStartPosition); + } + + _asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind); + arguments.Offset = va; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + else + { + _asContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated); + } + } + + return result; + } + + private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments) + { + int vaRegionStructSize = Unsafe.SizeOf<VaRegion>(); + + Debug.Assert(vaRegionStructSize == 0x18); + Debug.Assert(_pageSizes.Length == 2); + + uint writeEntries = (uint)(arguments.BufferSize / vaRegionStructSize); + if (writeEntries > _pageSizes.Length) + { + writeEntries = (uint)_pageSizes.Length; + } + + for (uint i = 0; i < writeEntries; i++) + { + ref var region = ref arguments.Regions[(int)i]; + + var vmRegion = _vmRegions[i]; + uint pageSize = _pageSizes[i]; + + region.PageSize = pageSize; + region.Offset = vmRegion.Start; + region.Pages = (vmRegion.Limit - vmRegion.Start) / pageSize; + region.Padding = 0; + } + + arguments.BufferSize = (uint)(_pageSizes.Length * vaRegionStructSize); + + return NvInternalResult.Success; + } + + private NvInternalResult InitializeEx(ref InitializeExArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult Remap(Span<RemapArguments> arguments) + { + MemoryManager gmm = _asContext.Gmm; + + for (int index = 0; index < arguments.Length; index++) + { + ref RemapArguments argument = ref arguments[index]; + ulong gpuVa = (ulong)argument.GpuOffset << 16; + ulong size = (ulong)argument.Pages << 16; + int nvmapHandle = argument.NvMapHandle; + + if (nvmapHandle == 0) + { + gmm.Unmap(gpuVa, size); + } + else + { + ulong mapOffs = (ulong)argument.MapOffset << 16; + PteKind kind = (PteKind)argument.Kind; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + gmm.Map(mapOffs + map.Address, gpuVa, size, kind); + } + } + + return NvInternalResult.Success; + } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs new file mode 100644 index 00000000..ab9d798e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -0,0 +1,190 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + class AddressSpaceContext + { + private class Range + { + public ulong Start { get; } + public ulong End { get; } + + public Range(ulong address, ulong size) + { + Start = address; + End = size + Start; + } + } + + private class MappedMemory : Range + { + public ulong PhysicalAddress { get; } + public bool VaAllocated { get; } + + public MappedMemory(ulong address, ulong size, ulong physicalAddress, bool vaAllocated) : base(address, size) + { + PhysicalAddress = physicalAddress; + VaAllocated = vaAllocated; + } + } + + public MemoryManager Gmm { get; } + + private readonly SortedList<ulong, Range> _maps; + private readonly SortedList<ulong, Range> _reservations; + + public AddressSpaceContext(MemoryManager gmm) + { + Gmm = gmm; + + _maps = new SortedList<ulong, Range>(); + _reservations = new SortedList<ulong, Range>(); + } + + public bool ValidateFixedBuffer(ulong address, ulong size, ulong alignment) + { + ulong mapEnd = address + size; + + // Check if size is valid (0 is also not allowed). + if (mapEnd <= address) + { + return false; + } + + // Check if address is aligned. + if ((address & (alignment - 1)) != 0) + { + return false; + } + + // Check if region is reserved. + if (BinarySearch(_reservations, address) == null) + { + return false; + } + + // Check for overlap with already mapped buffers. + Range map = BinarySearchLt(_maps, mapEnd); + + if (map != null && map.End > address) + { + return false; + } + + return true; + } + + public void AddMap(ulong gpuVa, ulong size, ulong physicalAddress, bool vaAllocated) + { + _maps.Add(gpuVa, new MappedMemory(gpuVa, size, physicalAddress, vaAllocated)); + } + + public bool RemoveMap(ulong gpuVa, out ulong size) + { + size = 0; + + if (_maps.Remove(gpuVa, out Range value)) + { + MappedMemory map = (MappedMemory)value; + + if (map.VaAllocated) + { + size = (map.End - map.Start); + } + + return true; + } + + return false; + } + + public bool TryGetMapPhysicalAddress(ulong gpuVa, out ulong physicalAddress) + { + Range map = BinarySearch(_maps, gpuVa); + + if (map != null) + { + physicalAddress = ((MappedMemory)map).PhysicalAddress; + return true; + } + + physicalAddress = 0; + return false; + } + + public void AddReservation(ulong gpuVa, ulong size) + { + _reservations.Add(gpuVa, new Range(gpuVa, size)); + } + + public bool RemoveReservation(ulong gpuVa) + { + return _reservations.Remove(gpuVa); + } + + private static Range BinarySearch(SortedList<ulong, Range> list, ulong address) + { + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address >= rg.Start && address < rg.End) + { + return rg; + } + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return null; + } + + private static Range BinarySearchLt(SortedList<ulong, Range> list, ulong address) + { + Range ltRg = null; + + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + + if (address > rg.Start) + { + ltRg = rg; + } + } + } + + return ltRg; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs new file mode 100644 index 00000000..611cf78b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [Flags] + enum AddressSpaceFlags : uint + { + FixedOffset = 1, + RemapSubRange = 0x100, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs new file mode 100644 index 00000000..d6dbbc26 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocSpaceArguments + { + public uint Pages; + public uint PageSize; + public AddressSpaceFlags Flags; + public uint Padding; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs new file mode 100644 index 00000000..9c6568a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct BindChannelArguments + { + public int Fd; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs new file mode 100644 index 00000000..b25d295a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct FreeSpaceArguments + { + public ulong Offset; + public uint Pages; + public uint PageSize; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs new file mode 100644 index 00000000..dcb5b49e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VaRegion + { + public ulong Offset; + public uint PageSize; + public uint Padding; + public ulong Pages; + } + + [StructLayout(LayoutKind.Sequential)] + struct GetVaRegionsArguments + { + public ulong Unused; + public uint BufferSize; + public uint Padding; + public Array2<VaRegion> Regions; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs new file mode 100644 index 00000000..882bda59 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InitializeExArguments + { + public uint Flags; + public int AsFd; + public uint BigPageSize; + public uint Reserved; + public ulong Unknown0; + public ulong Unknown1; + public ulong Unknown2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs new file mode 100644 index 00000000..278793a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct MapBufferExArguments + { + public AddressSpaceFlags Flags; + public int Kind; + public int NvMapHandle; + public int PageSize; + public ulong BufferOffset; + public ulong MappingSize; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs new file mode 100644 index 00000000..bc149d42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct RemapArguments + { + public ushort Flags; + public ushort Kind; + public int NvMapHandle; + public uint MapOffset; + public uint GpuOffset; + public uint Pages; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs new file mode 100644 index 00000000..8fc4646e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + struct UnmapBufferArguments + { +#pragma warning disable CS0649 + public ulong Offset; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs new file mode 100644 index 00000000..87a06bd3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs @@ -0,0 +1,1361 @@ +using Ryujinx.Graphics.Gpu; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + static class ChannelInitialization + { + public static void InitializeState(GpuChannel channel) + { + channel.Write(ClassId.Threed, 0x800, 0x0); + channel.Write(ClassId.Threed, 0x840, 0x0); + channel.Write(ClassId.Threed, 0x880, 0x0); + channel.Write(ClassId.Threed, 0x8C0, 0x0); + channel.Write(ClassId.Threed, 0x900, 0x0); + channel.Write(ClassId.Threed, 0x940, 0x0); + channel.Write(ClassId.Threed, 0x980, 0x0); + channel.Write(ClassId.Threed, 0x9C0, 0x0); + channel.Write(ClassId.Threed, 0x804, 0x0); + channel.Write(ClassId.Threed, 0x844, 0x0); + channel.Write(ClassId.Threed, 0x884, 0x0); + channel.Write(ClassId.Threed, 0x8C4, 0x0); + channel.Write(ClassId.Threed, 0x904, 0x0); + channel.Write(ClassId.Threed, 0x944, 0x0); + channel.Write(ClassId.Threed, 0x984, 0x0); + channel.Write(ClassId.Threed, 0x9C4, 0x0); + channel.Write(ClassId.Threed, 0x808, 0x400); + channel.Write(ClassId.Threed, 0x848, 0x400); + channel.Write(ClassId.Threed, 0x888, 0x400); + channel.Write(ClassId.Threed, 0x8C8, 0x400); + channel.Write(ClassId.Threed, 0x908, 0x400); + channel.Write(ClassId.Threed, 0x948, 0x400); + channel.Write(ClassId.Threed, 0x988, 0x400); + channel.Write(ClassId.Threed, 0x9C8, 0x400); + channel.Write(ClassId.Threed, 0x80C, 0x300); + channel.Write(ClassId.Threed, 0x84C, 0x300); + channel.Write(ClassId.Threed, 0x88C, 0x300); + channel.Write(ClassId.Threed, 0x8CC, 0x300); + channel.Write(ClassId.Threed, 0x90C, 0x300); + channel.Write(ClassId.Threed, 0x94C, 0x300); + channel.Write(ClassId.Threed, 0x98C, 0x300); + channel.Write(ClassId.Threed, 0x9CC, 0x300); + channel.Write(ClassId.Threed, 0x810, 0xCF); + channel.Write(ClassId.Threed, 0x850, 0x0); + channel.Write(ClassId.Threed, 0x890, 0x0); + channel.Write(ClassId.Threed, 0x8D0, 0x0); + channel.Write(ClassId.Threed, 0x910, 0x0); + channel.Write(ClassId.Threed, 0x950, 0x0); + channel.Write(ClassId.Threed, 0x990, 0x0); + channel.Write(ClassId.Threed, 0x9D0, 0x0); + channel.Write(ClassId.Threed, 0x814, 0x40); + channel.Write(ClassId.Threed, 0x854, 0x40); + channel.Write(ClassId.Threed, 0x894, 0x40); + channel.Write(ClassId.Threed, 0x8D4, 0x40); + channel.Write(ClassId.Threed, 0x914, 0x40); + channel.Write(ClassId.Threed, 0x954, 0x40); + channel.Write(ClassId.Threed, 0x994, 0x40); + channel.Write(ClassId.Threed, 0x9D4, 0x40); + channel.Write(ClassId.Threed, 0x818, 0x1); + channel.Write(ClassId.Threed, 0x858, 0x1); + channel.Write(ClassId.Threed, 0x898, 0x1); + channel.Write(ClassId.Threed, 0x8D8, 0x1); + channel.Write(ClassId.Threed, 0x918, 0x1); + channel.Write(ClassId.Threed, 0x958, 0x1); + channel.Write(ClassId.Threed, 0x998, 0x1); + channel.Write(ClassId.Threed, 0x9D8, 0x1); + channel.Write(ClassId.Threed, 0x81C, 0x0); + channel.Write(ClassId.Threed, 0x85C, 0x0); + channel.Write(ClassId.Threed, 0x89C, 0x0); + channel.Write(ClassId.Threed, 0x8DC, 0x0); + channel.Write(ClassId.Threed, 0x91C, 0x0); + channel.Write(ClassId.Threed, 0x95C, 0x0); + channel.Write(ClassId.Threed, 0x99C, 0x0); + channel.Write(ClassId.Threed, 0x9DC, 0x0); + channel.Write(ClassId.Threed, 0x820, 0x0); + channel.Write(ClassId.Threed, 0x860, 0x0); + channel.Write(ClassId.Threed, 0x8A0, 0x0); + channel.Write(ClassId.Threed, 0x8E0, 0x0); + channel.Write(ClassId.Threed, 0x920, 0x0); + channel.Write(ClassId.Threed, 0x960, 0x0); + channel.Write(ClassId.Threed, 0x9A0, 0x0); + channel.Write(ClassId.Threed, 0x9E0, 0x0); + channel.Write(ClassId.Threed, 0x1C00, 0x0); + channel.Write(ClassId.Threed, 0x1C10, 0x0); + channel.Write(ClassId.Threed, 0x1C20, 0x0); + channel.Write(ClassId.Threed, 0x1C30, 0x0); + channel.Write(ClassId.Threed, 0x1C40, 0x0); + channel.Write(ClassId.Threed, 0x1C50, 0x0); + channel.Write(ClassId.Threed, 0x1C60, 0x0); + channel.Write(ClassId.Threed, 0x1C70, 0x0); + channel.Write(ClassId.Threed, 0x1C80, 0x0); + channel.Write(ClassId.Threed, 0x1C90, 0x0); + channel.Write(ClassId.Threed, 0x1CA0, 0x0); + channel.Write(ClassId.Threed, 0x1CB0, 0x0); + channel.Write(ClassId.Threed, 0x1CC0, 0x0); + channel.Write(ClassId.Threed, 0x1CD0, 0x0); + channel.Write(ClassId.Threed, 0x1CE0, 0x0); + channel.Write(ClassId.Threed, 0x1CF0, 0x0); + channel.Write(ClassId.Threed, 0x1C04, 0x0); + channel.Write(ClassId.Threed, 0x1C14, 0x0); + channel.Write(ClassId.Threed, 0x1C24, 0x0); + channel.Write(ClassId.Threed, 0x1C34, 0x0); + channel.Write(ClassId.Threed, 0x1C44, 0x0); + channel.Write(ClassId.Threed, 0x1C54, 0x0); + channel.Write(ClassId.Threed, 0x1C64, 0x0); + channel.Write(ClassId.Threed, 0x1C74, 0x0); + channel.Write(ClassId.Threed, 0x1C84, 0x0); + channel.Write(ClassId.Threed, 0x1C94, 0x0); + channel.Write(ClassId.Threed, 0x1CA4, 0x0); + channel.Write(ClassId.Threed, 0x1CB4, 0x0); + channel.Write(ClassId.Threed, 0x1CC4, 0x0); + channel.Write(ClassId.Threed, 0x1CD4, 0x0); + channel.Write(ClassId.Threed, 0x1CE4, 0x0); + channel.Write(ClassId.Threed, 0x1CF4, 0x0); + channel.Write(ClassId.Threed, 0x1C08, 0x0); + channel.Write(ClassId.Threed, 0x1C18, 0x0); + channel.Write(ClassId.Threed, 0x1C28, 0x0); + channel.Write(ClassId.Threed, 0x1C38, 0x0); + channel.Write(ClassId.Threed, 0x1C48, 0x0); + channel.Write(ClassId.Threed, 0x1C58, 0x0); + channel.Write(ClassId.Threed, 0x1C68, 0x0); + channel.Write(ClassId.Threed, 0x1C78, 0x0); + channel.Write(ClassId.Threed, 0x1C88, 0x0); + channel.Write(ClassId.Threed, 0x1C98, 0x0); + channel.Write(ClassId.Threed, 0x1CA8, 0x0); + channel.Write(ClassId.Threed, 0x1CB8, 0x0); + channel.Write(ClassId.Threed, 0x1CC8, 0x0); + channel.Write(ClassId.Threed, 0x1CD8, 0x0); + channel.Write(ClassId.Threed, 0x1CE8, 0x0); + channel.Write(ClassId.Threed, 0x1CF8, 0x0); + channel.Write(ClassId.Threed, 0x1C0C, 0x0); + channel.Write(ClassId.Threed, 0x1C1C, 0x0); + channel.Write(ClassId.Threed, 0x1C2C, 0x0); + channel.Write(ClassId.Threed, 0x1C3C, 0x0); + channel.Write(ClassId.Threed, 0x1C4C, 0x0); + channel.Write(ClassId.Threed, 0x1C5C, 0x0); + channel.Write(ClassId.Threed, 0x1C6C, 0x0); + channel.Write(ClassId.Threed, 0x1C7C, 0x0); + channel.Write(ClassId.Threed, 0x1C8C, 0x0); + channel.Write(ClassId.Threed, 0x1C9C, 0x0); + channel.Write(ClassId.Threed, 0x1CAC, 0x0); + channel.Write(ClassId.Threed, 0x1CBC, 0x0); + channel.Write(ClassId.Threed, 0x1CCC, 0x0); + channel.Write(ClassId.Threed, 0x1CDC, 0x0); + channel.Write(ClassId.Threed, 0x1CEC, 0x0); + channel.Write(ClassId.Threed, 0x1CFC, 0x0); + channel.Write(ClassId.Threed, 0x1D00, 0x0); + channel.Write(ClassId.Threed, 0x1D10, 0x0); + channel.Write(ClassId.Threed, 0x1D20, 0x0); + channel.Write(ClassId.Threed, 0x1D30, 0x0); + channel.Write(ClassId.Threed, 0x1D40, 0x0); + channel.Write(ClassId.Threed, 0x1D50, 0x0); + channel.Write(ClassId.Threed, 0x1D60, 0x0); + channel.Write(ClassId.Threed, 0x1D70, 0x0); + channel.Write(ClassId.Threed, 0x1D80, 0x0); + channel.Write(ClassId.Threed, 0x1D90, 0x0); + channel.Write(ClassId.Threed, 0x1DA0, 0x0); + channel.Write(ClassId.Threed, 0x1DB0, 0x0); + channel.Write(ClassId.Threed, 0x1DC0, 0x0); + channel.Write(ClassId.Threed, 0x1DD0, 0x0); + channel.Write(ClassId.Threed, 0x1DE0, 0x0); + channel.Write(ClassId.Threed, 0x1DF0, 0x0); + channel.Write(ClassId.Threed, 0x1D04, 0x0); + channel.Write(ClassId.Threed, 0x1D14, 0x0); + channel.Write(ClassId.Threed, 0x1D24, 0x0); + channel.Write(ClassId.Threed, 0x1D34, 0x0); + channel.Write(ClassId.Threed, 0x1D44, 0x0); + channel.Write(ClassId.Threed, 0x1D54, 0x0); + channel.Write(ClassId.Threed, 0x1D64, 0x0); + channel.Write(ClassId.Threed, 0x1D74, 0x0); + channel.Write(ClassId.Threed, 0x1D84, 0x0); + channel.Write(ClassId.Threed, 0x1D94, 0x0); + channel.Write(ClassId.Threed, 0x1DA4, 0x0); + channel.Write(ClassId.Threed, 0x1DB4, 0x0); + channel.Write(ClassId.Threed, 0x1DC4, 0x0); + channel.Write(ClassId.Threed, 0x1DD4, 0x0); + channel.Write(ClassId.Threed, 0x1DE4, 0x0); + channel.Write(ClassId.Threed, 0x1DF4, 0x0); + channel.Write(ClassId.Threed, 0x1D08, 0x0); + channel.Write(ClassId.Threed, 0x1D18, 0x0); + channel.Write(ClassId.Threed, 0x1D28, 0x0); + channel.Write(ClassId.Threed, 0x1D38, 0x0); + channel.Write(ClassId.Threed, 0x1D48, 0x0); + channel.Write(ClassId.Threed, 0x1D58, 0x0); + channel.Write(ClassId.Threed, 0x1D68, 0x0); + channel.Write(ClassId.Threed, 0x1D78, 0x0); + channel.Write(ClassId.Threed, 0x1D88, 0x0); + channel.Write(ClassId.Threed, 0x1D98, 0x0); + channel.Write(ClassId.Threed, 0x1DA8, 0x0); + channel.Write(ClassId.Threed, 0x1DB8, 0x0); + channel.Write(ClassId.Threed, 0x1DC8, 0x0); + channel.Write(ClassId.Threed, 0x1DD8, 0x0); + channel.Write(ClassId.Threed, 0x1DE8, 0x0); + channel.Write(ClassId.Threed, 0x1DF8, 0x0); + channel.Write(ClassId.Threed, 0x1D0C, 0x0); + channel.Write(ClassId.Threed, 0x1D1C, 0x0); + channel.Write(ClassId.Threed, 0x1D2C, 0x0); + channel.Write(ClassId.Threed, 0x1D3C, 0x0); + channel.Write(ClassId.Threed, 0x1D4C, 0x0); + channel.Write(ClassId.Threed, 0x1D5C, 0x0); + channel.Write(ClassId.Threed, 0x1D6C, 0x0); + channel.Write(ClassId.Threed, 0x1D7C, 0x0); + channel.Write(ClassId.Threed, 0x1D8C, 0x0); + channel.Write(ClassId.Threed, 0x1D9C, 0x0); + channel.Write(ClassId.Threed, 0x1DAC, 0x0); + channel.Write(ClassId.Threed, 0x1DBC, 0x0); + channel.Write(ClassId.Threed, 0x1DCC, 0x0); + channel.Write(ClassId.Threed, 0x1DDC, 0x0); + channel.Write(ClassId.Threed, 0x1DEC, 0x0); + channel.Write(ClassId.Threed, 0x1DFC, 0x0); + channel.Write(ClassId.Threed, 0x1F00, 0x0); + channel.Write(ClassId.Threed, 0x1F08, 0x0); + channel.Write(ClassId.Threed, 0x1F10, 0x0); + channel.Write(ClassId.Threed, 0x1F18, 0x0); + channel.Write(ClassId.Threed, 0x1F20, 0x0); + channel.Write(ClassId.Threed, 0x1F28, 0x0); + channel.Write(ClassId.Threed, 0x1F30, 0x0); + channel.Write(ClassId.Threed, 0x1F38, 0x0); + channel.Write(ClassId.Threed, 0x1F40, 0x0); + channel.Write(ClassId.Threed, 0x1F48, 0x0); + channel.Write(ClassId.Threed, 0x1F50, 0x0); + channel.Write(ClassId.Threed, 0x1F58, 0x0); + channel.Write(ClassId.Threed, 0x1F60, 0x0); + channel.Write(ClassId.Threed, 0x1F68, 0x0); + channel.Write(ClassId.Threed, 0x1F70, 0x0); + channel.Write(ClassId.Threed, 0x1F78, 0x0); + channel.Write(ClassId.Threed, 0x1F04, 0x0); + channel.Write(ClassId.Threed, 0x1F0C, 0x0); + channel.Write(ClassId.Threed, 0x1F14, 0x0); + channel.Write(ClassId.Threed, 0x1F1C, 0x0); + channel.Write(ClassId.Threed, 0x1F24, 0x0); + channel.Write(ClassId.Threed, 0x1F2C, 0x0); + channel.Write(ClassId.Threed, 0x1F34, 0x0); + channel.Write(ClassId.Threed, 0x1F3C, 0x0); + channel.Write(ClassId.Threed, 0x1F44, 0x0); + channel.Write(ClassId.Threed, 0x1F4C, 0x0); + channel.Write(ClassId.Threed, 0x1F54, 0x0); + channel.Write(ClassId.Threed, 0x1F5C, 0x0); + channel.Write(ClassId.Threed, 0x1F64, 0x0); + channel.Write(ClassId.Threed, 0x1F6C, 0x0); + channel.Write(ClassId.Threed, 0x1F74, 0x0); + channel.Write(ClassId.Threed, 0x1F7C, 0x0); + channel.Write(ClassId.Threed, 0x1F80, 0x0); + channel.Write(ClassId.Threed, 0x1F88, 0x0); + channel.Write(ClassId.Threed, 0x1F90, 0x0); + channel.Write(ClassId.Threed, 0x1F98, 0x0); + channel.Write(ClassId.Threed, 0x1FA0, 0x0); + channel.Write(ClassId.Threed, 0x1FA8, 0x0); + channel.Write(ClassId.Threed, 0x1FB0, 0x0); + channel.Write(ClassId.Threed, 0x1FB8, 0x0); + channel.Write(ClassId.Threed, 0x1FC0, 0x0); + channel.Write(ClassId.Threed, 0x1FC8, 0x0); + channel.Write(ClassId.Threed, 0x1FD0, 0x0); + channel.Write(ClassId.Threed, 0x1FD8, 0x0); + channel.Write(ClassId.Threed, 0x1FE0, 0x0); + channel.Write(ClassId.Threed, 0x1FE8, 0x0); + channel.Write(ClassId.Threed, 0x1FF0, 0x0); + channel.Write(ClassId.Threed, 0x1FF8, 0x0); + channel.Write(ClassId.Threed, 0x1F84, 0x0); + channel.Write(ClassId.Threed, 0x1F8C, 0x0); + channel.Write(ClassId.Threed, 0x1F94, 0x0); + channel.Write(ClassId.Threed, 0x1F9C, 0x0); + channel.Write(ClassId.Threed, 0x1FA4, 0x0); + channel.Write(ClassId.Threed, 0x1FAC, 0x0); + channel.Write(ClassId.Threed, 0x1FB4, 0x0); + channel.Write(ClassId.Threed, 0x1FBC, 0x0); + channel.Write(ClassId.Threed, 0x1FC4, 0x0); + channel.Write(ClassId.Threed, 0x1FCC, 0x0); + channel.Write(ClassId.Threed, 0x1FD4, 0x0); + channel.Write(ClassId.Threed, 0x1FDC, 0x0); + channel.Write(ClassId.Threed, 0x1FE4, 0x0); + channel.Write(ClassId.Threed, 0x1FEC, 0x0); + channel.Write(ClassId.Threed, 0x1FF4, 0x0); + channel.Write(ClassId.Threed, 0x1FFC, 0x0); + channel.Write(ClassId.Threed, 0x2000, 0x0); + channel.Write(ClassId.Threed, 0x2040, 0x11); + channel.Write(ClassId.Threed, 0x2080, 0x20); + channel.Write(ClassId.Threed, 0x20C0, 0x30); + channel.Write(ClassId.Threed, 0x2100, 0x40); + channel.Write(ClassId.Threed, 0x2140, 0x51); + channel.Write(ClassId.Threed, 0x200C, 0x1); + channel.Write(ClassId.Threed, 0x204C, 0x1); + channel.Write(ClassId.Threed, 0x208C, 0x1); + channel.Write(ClassId.Threed, 0x20CC, 0x1); + channel.Write(ClassId.Threed, 0x210C, 0x1); + channel.Write(ClassId.Threed, 0x214C, 0x1); + channel.Write(ClassId.Threed, 0x2010, 0x0); + channel.Write(ClassId.Threed, 0x2050, 0x0); + channel.Write(ClassId.Threed, 0x2090, 0x1); + channel.Write(ClassId.Threed, 0x20D0, 0x2); + channel.Write(ClassId.Threed, 0x2110, 0x3); + channel.Write(ClassId.Threed, 0x2150, 0x4); + channel.Write(ClassId.Threed, 0x380, 0x0); + channel.Write(ClassId.Threed, 0x3A0, 0x0); + channel.Write(ClassId.Threed, 0x3C0, 0x0); + channel.Write(ClassId.Threed, 0x3E0, 0x0); + channel.Write(ClassId.Threed, 0x384, 0x0); + channel.Write(ClassId.Threed, 0x3A4, 0x0); + channel.Write(ClassId.Threed, 0x3C4, 0x0); + channel.Write(ClassId.Threed, 0x3E4, 0x0); + channel.Write(ClassId.Threed, 0x388, 0x0); + channel.Write(ClassId.Threed, 0x3A8, 0x0); + channel.Write(ClassId.Threed, 0x3C8, 0x0); + channel.Write(ClassId.Threed, 0x3E8, 0x0); + channel.Write(ClassId.Threed, 0x38C, 0x0); + channel.Write(ClassId.Threed, 0x3AC, 0x0); + channel.Write(ClassId.Threed, 0x3CC, 0x0); + channel.Write(ClassId.Threed, 0x3EC, 0x0); + channel.Write(ClassId.Threed, 0x700, 0x0); + channel.Write(ClassId.Threed, 0x710, 0x0); + channel.Write(ClassId.Threed, 0x720, 0x0); + channel.Write(ClassId.Threed, 0x730, 0x0); + channel.Write(ClassId.Threed, 0x704, 0x0); + channel.Write(ClassId.Threed, 0x714, 0x0); + channel.Write(ClassId.Threed, 0x724, 0x0); + channel.Write(ClassId.Threed, 0x734, 0x0); + channel.Write(ClassId.Threed, 0x708, 0x0); + channel.Write(ClassId.Threed, 0x718, 0x0); + channel.Write(ClassId.Threed, 0x728, 0x0); + channel.Write(ClassId.Threed, 0x738, 0x0); + channel.Write(ClassId.Threed, 0x2800, 0x0); + channel.Write(ClassId.Threed, 0x2804, 0x0); + channel.Write(ClassId.Threed, 0x2808, 0x0); + channel.Write(ClassId.Threed, 0x280C, 0x0); + channel.Write(ClassId.Threed, 0x2810, 0x0); + channel.Write(ClassId.Threed, 0x2814, 0x0); + channel.Write(ClassId.Threed, 0x2818, 0x0); + channel.Write(ClassId.Threed, 0x281C, 0x0); + channel.Write(ClassId.Threed, 0x2820, 0x0); + channel.Write(ClassId.Threed, 0x2824, 0x0); + channel.Write(ClassId.Threed, 0x2828, 0x0); + channel.Write(ClassId.Threed, 0x282C, 0x0); + channel.Write(ClassId.Threed, 0x2830, 0x0); + channel.Write(ClassId.Threed, 0x2834, 0x0); + channel.Write(ClassId.Threed, 0x2838, 0x0); + channel.Write(ClassId.Threed, 0x283C, 0x0); + channel.Write(ClassId.Threed, 0x2840, 0x0); + channel.Write(ClassId.Threed, 0x2844, 0x0); + channel.Write(ClassId.Threed, 0x2848, 0x0); + channel.Write(ClassId.Threed, 0x284C, 0x0); + channel.Write(ClassId.Threed, 0x2850, 0x0); + channel.Write(ClassId.Threed, 0x2854, 0x0); + channel.Write(ClassId.Threed, 0x2858, 0x0); + channel.Write(ClassId.Threed, 0x285C, 0x0); + channel.Write(ClassId.Threed, 0x2860, 0x0); + channel.Write(ClassId.Threed, 0x2864, 0x0); + channel.Write(ClassId.Threed, 0x2868, 0x0); + channel.Write(ClassId.Threed, 0x286C, 0x0); + channel.Write(ClassId.Threed, 0x2870, 0x0); + channel.Write(ClassId.Threed, 0x2874, 0x0); + channel.Write(ClassId.Threed, 0x2878, 0x0); + channel.Write(ClassId.Threed, 0x287C, 0x0); + channel.Write(ClassId.Threed, 0x2880, 0x0); + channel.Write(ClassId.Threed, 0x2884, 0x0); + channel.Write(ClassId.Threed, 0x2888, 0x0); + channel.Write(ClassId.Threed, 0x288C, 0x0); + channel.Write(ClassId.Threed, 0x2890, 0x0); + channel.Write(ClassId.Threed, 0x2894, 0x0); + channel.Write(ClassId.Threed, 0x2898, 0x0); + channel.Write(ClassId.Threed, 0x289C, 0x0); + channel.Write(ClassId.Threed, 0x28A0, 0x0); + channel.Write(ClassId.Threed, 0x28A4, 0x0); + channel.Write(ClassId.Threed, 0x28A8, 0x0); + channel.Write(ClassId.Threed, 0x28AC, 0x0); + channel.Write(ClassId.Threed, 0x28B0, 0x0); + channel.Write(ClassId.Threed, 0x28B4, 0x0); + channel.Write(ClassId.Threed, 0x28B8, 0x0); + channel.Write(ClassId.Threed, 0x28BC, 0x0); + channel.Write(ClassId.Threed, 0x28C0, 0x0); + channel.Write(ClassId.Threed, 0x28C4, 0x0); + channel.Write(ClassId.Threed, 0x28C8, 0x0); + channel.Write(ClassId.Threed, 0x28CC, 0x0); + channel.Write(ClassId.Threed, 0x28D0, 0x0); + channel.Write(ClassId.Threed, 0x28D4, 0x0); + channel.Write(ClassId.Threed, 0x28D8, 0x0); + channel.Write(ClassId.Threed, 0x28DC, 0x0); + channel.Write(ClassId.Threed, 0x28E0, 0x0); + channel.Write(ClassId.Threed, 0x28E4, 0x0); + channel.Write(ClassId.Threed, 0x28E8, 0x0); + channel.Write(ClassId.Threed, 0x28EC, 0x0); + channel.Write(ClassId.Threed, 0x28F0, 0x0); + channel.Write(ClassId.Threed, 0x28F4, 0x0); + channel.Write(ClassId.Threed, 0x28F8, 0x0); + channel.Write(ClassId.Threed, 0x28FC, 0x0); + channel.Write(ClassId.Threed, 0x2900, 0x0); + channel.Write(ClassId.Threed, 0x2904, 0x0); + channel.Write(ClassId.Threed, 0x2908, 0x0); + channel.Write(ClassId.Threed, 0x290C, 0x0); + channel.Write(ClassId.Threed, 0x2910, 0x0); + channel.Write(ClassId.Threed, 0x2914, 0x0); + channel.Write(ClassId.Threed, 0x2918, 0x0); + channel.Write(ClassId.Threed, 0x291C, 0x0); + channel.Write(ClassId.Threed, 0x2920, 0x0); + channel.Write(ClassId.Threed, 0x2924, 0x0); + channel.Write(ClassId.Threed, 0x2928, 0x0); + channel.Write(ClassId.Threed, 0x292C, 0x0); + channel.Write(ClassId.Threed, 0x2930, 0x0); + channel.Write(ClassId.Threed, 0x2934, 0x0); + channel.Write(ClassId.Threed, 0x2938, 0x0); + channel.Write(ClassId.Threed, 0x293C, 0x0); + channel.Write(ClassId.Threed, 0x2940, 0x0); + channel.Write(ClassId.Threed, 0x2944, 0x0); + channel.Write(ClassId.Threed, 0x2948, 0x0); + channel.Write(ClassId.Threed, 0x294C, 0x0); + channel.Write(ClassId.Threed, 0x2950, 0x0); + channel.Write(ClassId.Threed, 0x2954, 0x0); + channel.Write(ClassId.Threed, 0x2958, 0x0); + channel.Write(ClassId.Threed, 0x295C, 0x0); + channel.Write(ClassId.Threed, 0x2960, 0x0); + channel.Write(ClassId.Threed, 0x2964, 0x0); + channel.Write(ClassId.Threed, 0x2968, 0x0); + channel.Write(ClassId.Threed, 0x296C, 0x0); + channel.Write(ClassId.Threed, 0x2970, 0x0); + channel.Write(ClassId.Threed, 0x2974, 0x0); + channel.Write(ClassId.Threed, 0x2978, 0x0); + channel.Write(ClassId.Threed, 0x297C, 0x0); + channel.Write(ClassId.Threed, 0x2980, 0x0); + channel.Write(ClassId.Threed, 0x2984, 0x0); + channel.Write(ClassId.Threed, 0x2988, 0x0); + channel.Write(ClassId.Threed, 0x298C, 0x0); + channel.Write(ClassId.Threed, 0x2990, 0x0); + channel.Write(ClassId.Threed, 0x2994, 0x0); + channel.Write(ClassId.Threed, 0x2998, 0x0); + channel.Write(ClassId.Threed, 0x299C, 0x0); + channel.Write(ClassId.Threed, 0x29A0, 0x0); + channel.Write(ClassId.Threed, 0x29A4, 0x0); + channel.Write(ClassId.Threed, 0x29A8, 0x0); + channel.Write(ClassId.Threed, 0x29AC, 0x0); + channel.Write(ClassId.Threed, 0x29B0, 0x0); + channel.Write(ClassId.Threed, 0x29B4, 0x0); + channel.Write(ClassId.Threed, 0x29B8, 0x0); + channel.Write(ClassId.Threed, 0x29BC, 0x0); + channel.Write(ClassId.Threed, 0x29C0, 0x0); + channel.Write(ClassId.Threed, 0x29C4, 0x0); + channel.Write(ClassId.Threed, 0x29C8, 0x0); + channel.Write(ClassId.Threed, 0x29CC, 0x0); + channel.Write(ClassId.Threed, 0x29D0, 0x0); + channel.Write(ClassId.Threed, 0x29D4, 0x0); + channel.Write(ClassId.Threed, 0x29D8, 0x0); + channel.Write(ClassId.Threed, 0x29DC, 0x0); + channel.Write(ClassId.Threed, 0x29E0, 0x0); + channel.Write(ClassId.Threed, 0x29E4, 0x0); + channel.Write(ClassId.Threed, 0x29E8, 0x0); + channel.Write(ClassId.Threed, 0x29EC, 0x0); + channel.Write(ClassId.Threed, 0x29F0, 0x0); + channel.Write(ClassId.Threed, 0x29F4, 0x0); + channel.Write(ClassId.Threed, 0x29F8, 0x0); + channel.Write(ClassId.Threed, 0x29FC, 0x0); + channel.Write(ClassId.Threed, 0xA00, 0x0); + channel.Write(ClassId.Threed, 0xA20, 0x0); + channel.Write(ClassId.Threed, 0xA40, 0x0); + channel.Write(ClassId.Threed, 0xA60, 0x0); + channel.Write(ClassId.Threed, 0xA80, 0x0); + channel.Write(ClassId.Threed, 0xAA0, 0x0); + channel.Write(ClassId.Threed, 0xAC0, 0x0); + channel.Write(ClassId.Threed, 0xAE0, 0x0); + channel.Write(ClassId.Threed, 0xB00, 0x0); + channel.Write(ClassId.Threed, 0xB20, 0x0); + channel.Write(ClassId.Threed, 0xB40, 0x0); + channel.Write(ClassId.Threed, 0xB60, 0x0); + channel.Write(ClassId.Threed, 0xB80, 0x0); + channel.Write(ClassId.Threed, 0xBA0, 0x0); + channel.Write(ClassId.Threed, 0xBC0, 0x0); + channel.Write(ClassId.Threed, 0xBE0, 0x0); + channel.Write(ClassId.Threed, 0xA04, 0x0); + channel.Write(ClassId.Threed, 0xA24, 0x0); + channel.Write(ClassId.Threed, 0xA44, 0x0); + channel.Write(ClassId.Threed, 0xA64, 0x0); + channel.Write(ClassId.Threed, 0xA84, 0x0); + channel.Write(ClassId.Threed, 0xAA4, 0x0); + channel.Write(ClassId.Threed, 0xAC4, 0x0); + channel.Write(ClassId.Threed, 0xAE4, 0x0); + channel.Write(ClassId.Threed, 0xB04, 0x0); + channel.Write(ClassId.Threed, 0xB24, 0x0); + channel.Write(ClassId.Threed, 0xB44, 0x0); + channel.Write(ClassId.Threed, 0xB64, 0x0); + channel.Write(ClassId.Threed, 0xB84, 0x0); + channel.Write(ClassId.Threed, 0xBA4, 0x0); + channel.Write(ClassId.Threed, 0xBC4, 0x0); + channel.Write(ClassId.Threed, 0xBE4, 0x0); + channel.Write(ClassId.Threed, 0xA08, 0x0); + channel.Write(ClassId.Threed, 0xA28, 0x0); + channel.Write(ClassId.Threed, 0xA48, 0x0); + channel.Write(ClassId.Threed, 0xA68, 0x0); + channel.Write(ClassId.Threed, 0xA88, 0x0); + channel.Write(ClassId.Threed, 0xAA8, 0x0); + channel.Write(ClassId.Threed, 0xAC8, 0x0); + channel.Write(ClassId.Threed, 0xAE8, 0x0); + channel.Write(ClassId.Threed, 0xB08, 0x0); + channel.Write(ClassId.Threed, 0xB28, 0x0); + channel.Write(ClassId.Threed, 0xB48, 0x0); + channel.Write(ClassId.Threed, 0xB68, 0x0); + channel.Write(ClassId.Threed, 0xB88, 0x0); + channel.Write(ClassId.Threed, 0xBA8, 0x0); + channel.Write(ClassId.Threed, 0xBC8, 0x0); + channel.Write(ClassId.Threed, 0xBE8, 0x0); + channel.Write(ClassId.Threed, 0xA0C, 0x0); + channel.Write(ClassId.Threed, 0xA2C, 0x0); + channel.Write(ClassId.Threed, 0xA4C, 0x0); + channel.Write(ClassId.Threed, 0xA6C, 0x0); + channel.Write(ClassId.Threed, 0xA8C, 0x0); + channel.Write(ClassId.Threed, 0xAAC, 0x0); + channel.Write(ClassId.Threed, 0xACC, 0x0); + channel.Write(ClassId.Threed, 0xAEC, 0x0); + channel.Write(ClassId.Threed, 0xB0C, 0x0); + channel.Write(ClassId.Threed, 0xB2C, 0x0); + channel.Write(ClassId.Threed, 0xB4C, 0x0); + channel.Write(ClassId.Threed, 0xB6C, 0x0); + channel.Write(ClassId.Threed, 0xB8C, 0x0); + channel.Write(ClassId.Threed, 0xBAC, 0x0); + channel.Write(ClassId.Threed, 0xBCC, 0x0); + channel.Write(ClassId.Threed, 0xBEC, 0x0); + channel.Write(ClassId.Threed, 0xA10, 0x0); + channel.Write(ClassId.Threed, 0xA30, 0x0); + channel.Write(ClassId.Threed, 0xA50, 0x0); + channel.Write(ClassId.Threed, 0xA70, 0x0); + channel.Write(ClassId.Threed, 0xA90, 0x0); + channel.Write(ClassId.Threed, 0xAB0, 0x0); + channel.Write(ClassId.Threed, 0xAD0, 0x0); + channel.Write(ClassId.Threed, 0xAF0, 0x0); + channel.Write(ClassId.Threed, 0xB10, 0x0); + channel.Write(ClassId.Threed, 0xB30, 0x0); + channel.Write(ClassId.Threed, 0xB50, 0x0); + channel.Write(ClassId.Threed, 0xB70, 0x0); + channel.Write(ClassId.Threed, 0xB90, 0x0); + channel.Write(ClassId.Threed, 0xBB0, 0x0); + channel.Write(ClassId.Threed, 0xBD0, 0x0); + channel.Write(ClassId.Threed, 0xBF0, 0x0); + channel.Write(ClassId.Threed, 0xA14, 0x0); + channel.Write(ClassId.Threed, 0xA34, 0x0); + channel.Write(ClassId.Threed, 0xA54, 0x0); + channel.Write(ClassId.Threed, 0xA74, 0x0); + channel.Write(ClassId.Threed, 0xA94, 0x0); + channel.Write(ClassId.Threed, 0xAB4, 0x0); + channel.Write(ClassId.Threed, 0xAD4, 0x0); + channel.Write(ClassId.Threed, 0xAF4, 0x0); + channel.Write(ClassId.Threed, 0xB14, 0x0); + channel.Write(ClassId.Threed, 0xB34, 0x0); + channel.Write(ClassId.Threed, 0xB54, 0x0); + channel.Write(ClassId.Threed, 0xB74, 0x0); + channel.Write(ClassId.Threed, 0xB94, 0x0); + channel.Write(ClassId.Threed, 0xBB4, 0x0); + channel.Write(ClassId.Threed, 0xBD4, 0x0); + channel.Write(ClassId.Threed, 0xBF4, 0x0); + channel.Write(ClassId.Threed, 0xA18, 0x6420); + channel.Write(ClassId.Threed, 0xA38, 0x6420); + channel.Write(ClassId.Threed, 0xA58, 0x6420); + channel.Write(ClassId.Threed, 0xA78, 0x6420); + channel.Write(ClassId.Threed, 0xA98, 0x6420); + channel.Write(ClassId.Threed, 0xAB8, 0x6420); + channel.Write(ClassId.Threed, 0xAD8, 0x6420); + channel.Write(ClassId.Threed, 0xAF8, 0x6420); + channel.Write(ClassId.Threed, 0xB18, 0x6420); + channel.Write(ClassId.Threed, 0xB38, 0x6420); + channel.Write(ClassId.Threed, 0xB58, 0x6420); + channel.Write(ClassId.Threed, 0xB78, 0x6420); + channel.Write(ClassId.Threed, 0xB98, 0x6420); + channel.Write(ClassId.Threed, 0xBB8, 0x6420); + channel.Write(ClassId.Threed, 0xBD8, 0x6420); + channel.Write(ClassId.Threed, 0xBF8, 0x6420); + channel.Write(ClassId.Threed, 0xA1C, 0x0); + channel.Write(ClassId.Threed, 0xA3C, 0x0); + channel.Write(ClassId.Threed, 0xA5C, 0x0); + channel.Write(ClassId.Threed, 0xA7C, 0x0); + channel.Write(ClassId.Threed, 0xA9C, 0x0); + channel.Write(ClassId.Threed, 0xABC, 0x0); + channel.Write(ClassId.Threed, 0xADC, 0x0); + channel.Write(ClassId.Threed, 0xAFC, 0x0); + channel.Write(ClassId.Threed, 0xB1C, 0x0); + channel.Write(ClassId.Threed, 0xB3C, 0x0); + channel.Write(ClassId.Threed, 0xB5C, 0x0); + channel.Write(ClassId.Threed, 0xB7C, 0x0); + channel.Write(ClassId.Threed, 0xB9C, 0x0); + channel.Write(ClassId.Threed, 0xBBC, 0x0); + channel.Write(ClassId.Threed, 0xBDC, 0x0); + channel.Write(ClassId.Threed, 0xBFC, 0x0); + channel.Write(ClassId.Threed, 0xC00, 0x0); + channel.Write(ClassId.Threed, 0xC10, 0x0); + channel.Write(ClassId.Threed, 0xC20, 0x0); + channel.Write(ClassId.Threed, 0xC30, 0x0); + channel.Write(ClassId.Threed, 0xC40, 0x0); + channel.Write(ClassId.Threed, 0xC50, 0x0); + channel.Write(ClassId.Threed, 0xC60, 0x0); + channel.Write(ClassId.Threed, 0xC70, 0x0); + channel.Write(ClassId.Threed, 0xC80, 0x0); + channel.Write(ClassId.Threed, 0xC90, 0x0); + channel.Write(ClassId.Threed, 0xCA0, 0x0); + channel.Write(ClassId.Threed, 0xCB0, 0x0); + channel.Write(ClassId.Threed, 0xCC0, 0x0); + channel.Write(ClassId.Threed, 0xCD0, 0x0); + channel.Write(ClassId.Threed, 0xCE0, 0x0); + channel.Write(ClassId.Threed, 0xCF0, 0x0); + channel.Write(ClassId.Threed, 0xC04, 0x0); + channel.Write(ClassId.Threed, 0xC14, 0x0); + channel.Write(ClassId.Threed, 0xC24, 0x0); + channel.Write(ClassId.Threed, 0xC34, 0x0); + channel.Write(ClassId.Threed, 0xC44, 0x0); + channel.Write(ClassId.Threed, 0xC54, 0x0); + channel.Write(ClassId.Threed, 0xC64, 0x0); + channel.Write(ClassId.Threed, 0xC74, 0x0); + channel.Write(ClassId.Threed, 0xC84, 0x0); + channel.Write(ClassId.Threed, 0xC94, 0x0); + channel.Write(ClassId.Threed, 0xCA4, 0x0); + channel.Write(ClassId.Threed, 0xCB4, 0x0); + channel.Write(ClassId.Threed, 0xCC4, 0x0); + channel.Write(ClassId.Threed, 0xCD4, 0x0); + channel.Write(ClassId.Threed, 0xCE4, 0x0); + channel.Write(ClassId.Threed, 0xCF4, 0x0); + channel.Write(ClassId.Threed, 0xC08, 0x0); + channel.Write(ClassId.Threed, 0xC18, 0x0); + channel.Write(ClassId.Threed, 0xC28, 0x0); + channel.Write(ClassId.Threed, 0xC38, 0x0); + channel.Write(ClassId.Threed, 0xC48, 0x0); + channel.Write(ClassId.Threed, 0xC58, 0x0); + channel.Write(ClassId.Threed, 0xC68, 0x0); + channel.Write(ClassId.Threed, 0xC78, 0x0); + channel.Write(ClassId.Threed, 0xC88, 0x0); + channel.Write(ClassId.Threed, 0xC98, 0x0); + channel.Write(ClassId.Threed, 0xCA8, 0x0); + channel.Write(ClassId.Threed, 0xCB8, 0x0); + channel.Write(ClassId.Threed, 0xCC8, 0x0); + channel.Write(ClassId.Threed, 0xCD8, 0x0); + channel.Write(ClassId.Threed, 0xCE8, 0x0); + channel.Write(ClassId.Threed, 0xCF8, 0x0); + channel.Write(ClassId.Threed, 0xC0C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC1C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC2C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC3C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC4C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC5C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC6C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC7C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC8C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC9C, 0x3F800000); + channel.Write(ClassId.Threed, 0xCAC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCBC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCCC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCDC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCEC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCFC, 0x3F800000); + channel.Write(ClassId.Threed, 0xD00, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD10, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD20, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD30, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD0C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD1C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD2C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD3C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE00, 0x0); + channel.Write(ClassId.Threed, 0xE10, 0x0); + channel.Write(ClassId.Threed, 0xE20, 0x0); + channel.Write(ClassId.Threed, 0xE30, 0x0); + channel.Write(ClassId.Threed, 0xE40, 0x0); + channel.Write(ClassId.Threed, 0xE50, 0x0); + channel.Write(ClassId.Threed, 0xE60, 0x0); + channel.Write(ClassId.Threed, 0xE70, 0x0); + channel.Write(ClassId.Threed, 0xE80, 0x0); + channel.Write(ClassId.Threed, 0xE90, 0x0); + channel.Write(ClassId.Threed, 0xEA0, 0x0); + channel.Write(ClassId.Threed, 0xEB0, 0x0); + channel.Write(ClassId.Threed, 0xEC0, 0x0); + channel.Write(ClassId.Threed, 0xED0, 0x0); + channel.Write(ClassId.Threed, 0xEE0, 0x0); + channel.Write(ClassId.Threed, 0xEF0, 0x0); + channel.Write(ClassId.Threed, 0xE04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE44, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE54, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE64, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE74, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE84, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE94, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE48, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE58, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE68, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE78, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE88, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE98, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD40, 0x0); + channel.Write(ClassId.Threed, 0xD48, 0x0); + channel.Write(ClassId.Threed, 0xD50, 0x0); + channel.Write(ClassId.Threed, 0xD58, 0x0); + channel.Write(ClassId.Threed, 0xD44, 0x0); + channel.Write(ClassId.Threed, 0xD4C, 0x0); + channel.Write(ClassId.Threed, 0xD54, 0x0); + channel.Write(ClassId.Threed, 0xD5C, 0x0); + channel.Write(ClassId.Threed, 0x1E00, 0x1); + channel.Write(ClassId.Threed, 0x1E20, 0x1); + channel.Write(ClassId.Threed, 0x1E40, 0x1); + channel.Write(ClassId.Threed, 0x1E60, 0x1); + channel.Write(ClassId.Threed, 0x1E80, 0x1); + channel.Write(ClassId.Threed, 0x1EA0, 0x1); + channel.Write(ClassId.Threed, 0x1EC0, 0x1); + channel.Write(ClassId.Threed, 0x1EE0, 0x1); + channel.Write(ClassId.Threed, 0x1E04, 0x1); + channel.Write(ClassId.Threed, 0x1E24, 0x1); + channel.Write(ClassId.Threed, 0x1E44, 0x1); + channel.Write(ClassId.Threed, 0x1E64, 0x1); + channel.Write(ClassId.Threed, 0x1E84, 0x1); + channel.Write(ClassId.Threed, 0x1EA4, 0x1); + channel.Write(ClassId.Threed, 0x1EC4, 0x1); + channel.Write(ClassId.Threed, 0x1EE4, 0x1); + channel.Write(ClassId.Threed, 0x1E08, 0x2); + channel.Write(ClassId.Threed, 0x1E28, 0x2); + channel.Write(ClassId.Threed, 0x1E48, 0x2); + channel.Write(ClassId.Threed, 0x1E68, 0x2); + channel.Write(ClassId.Threed, 0x1E88, 0x2); + channel.Write(ClassId.Threed, 0x1EA8, 0x2); + channel.Write(ClassId.Threed, 0x1EC8, 0x2); + channel.Write(ClassId.Threed, 0x1EE8, 0x2); + channel.Write(ClassId.Threed, 0x1E0C, 0x1); + channel.Write(ClassId.Threed, 0x1E2C, 0x1); + channel.Write(ClassId.Threed, 0x1E4C, 0x1); + channel.Write(ClassId.Threed, 0x1E6C, 0x1); + channel.Write(ClassId.Threed, 0x1E8C, 0x1); + channel.Write(ClassId.Threed, 0x1EAC, 0x1); + channel.Write(ClassId.Threed, 0x1ECC, 0x1); + channel.Write(ClassId.Threed, 0x1EEC, 0x1); + channel.Write(ClassId.Threed, 0x1E10, 0x1); + channel.Write(ClassId.Threed, 0x1E30, 0x1); + channel.Write(ClassId.Threed, 0x1E50, 0x1); + channel.Write(ClassId.Threed, 0x1E70, 0x1); + channel.Write(ClassId.Threed, 0x1E90, 0x1); + channel.Write(ClassId.Threed, 0x1EB0, 0x1); + channel.Write(ClassId.Threed, 0x1ED0, 0x1); + channel.Write(ClassId.Threed, 0x1EF0, 0x1); + channel.Write(ClassId.Threed, 0x1E14, 0x2); + channel.Write(ClassId.Threed, 0x1E34, 0x2); + channel.Write(ClassId.Threed, 0x1E54, 0x2); + channel.Write(ClassId.Threed, 0x1E74, 0x2); + channel.Write(ClassId.Threed, 0x1E94, 0x2); + channel.Write(ClassId.Threed, 0x1EB4, 0x2); + channel.Write(ClassId.Threed, 0x1ED4, 0x2); + channel.Write(ClassId.Threed, 0x1EF4, 0x2); + channel.Write(ClassId.Threed, 0x1E18, 0x1); + channel.Write(ClassId.Threed, 0x1E38, 0x1); + channel.Write(ClassId.Threed, 0x1E58, 0x1); + channel.Write(ClassId.Threed, 0x1E78, 0x1); + channel.Write(ClassId.Threed, 0x1E98, 0x1); + channel.Write(ClassId.Threed, 0x1EB8, 0x1); + channel.Write(ClassId.Threed, 0x1ED8, 0x1); + channel.Write(ClassId.Threed, 0x1EF8, 0x1); + channel.Write(ClassId.Threed, 0x1480, 0x0); + channel.Write(ClassId.Threed, 0x1490, 0x0); + channel.Write(ClassId.Threed, 0x14A0, 0x0); + channel.Write(ClassId.Threed, 0x14B0, 0x0); + channel.Write(ClassId.Threed, 0x14C0, 0x0); + channel.Write(ClassId.Threed, 0x14D0, 0x0); + channel.Write(ClassId.Threed, 0x14E0, 0x0); + channel.Write(ClassId.Threed, 0x14F0, 0x0); + channel.Write(ClassId.Threed, 0x1484, 0x0); + channel.Write(ClassId.Threed, 0x1494, 0x0); + channel.Write(ClassId.Threed, 0x14A4, 0x0); + channel.Write(ClassId.Threed, 0x14B4, 0x0); + channel.Write(ClassId.Threed, 0x14C4, 0x0); + channel.Write(ClassId.Threed, 0x14D4, 0x0); + channel.Write(ClassId.Threed, 0x14E4, 0x0); + channel.Write(ClassId.Threed, 0x14F4, 0x0); + channel.Write(ClassId.Threed, 0x1488, 0x0); + channel.Write(ClassId.Threed, 0x1498, 0x0); + channel.Write(ClassId.Threed, 0x14A8, 0x0); + channel.Write(ClassId.Threed, 0x14B8, 0x0); + channel.Write(ClassId.Threed, 0x14C8, 0x0); + channel.Write(ClassId.Threed, 0x14D8, 0x0); + channel.Write(ClassId.Threed, 0x14E8, 0x0); + channel.Write(ClassId.Threed, 0x14F8, 0x0); + channel.Write(ClassId.Threed, 0x3400, 0x0); + channel.Write(ClassId.Threed, 0x3404, 0x0); + channel.Write(ClassId.Threed, 0x3408, 0x0); + channel.Write(ClassId.Threed, 0x340C, 0x0); + channel.Write(ClassId.Threed, 0x3410, 0x0); + channel.Write(ClassId.Threed, 0x3414, 0x0); + channel.Write(ClassId.Threed, 0x3418, 0x0); + channel.Write(ClassId.Threed, 0x341C, 0x0); + channel.Write(ClassId.Threed, 0x3420, 0x0); + channel.Write(ClassId.Threed, 0x3424, 0x0); + channel.Write(ClassId.Threed, 0x3428, 0x0); + channel.Write(ClassId.Threed, 0x342C, 0x0); + channel.Write(ClassId.Threed, 0x3430, 0x0); + channel.Write(ClassId.Threed, 0x3434, 0x0); + channel.Write(ClassId.Threed, 0x3438, 0x0); + channel.Write(ClassId.Threed, 0x343C, 0x0); + channel.Write(ClassId.Threed, 0x3440, 0x0); + channel.Write(ClassId.Threed, 0x3444, 0x0); + channel.Write(ClassId.Threed, 0x3448, 0x0); + channel.Write(ClassId.Threed, 0x344C, 0x0); + channel.Write(ClassId.Threed, 0x3450, 0x0); + channel.Write(ClassId.Threed, 0x3454, 0x0); + channel.Write(ClassId.Threed, 0x3458, 0x0); + channel.Write(ClassId.Threed, 0x345C, 0x0); + channel.Write(ClassId.Threed, 0x3460, 0x0); + channel.Write(ClassId.Threed, 0x3464, 0x0); + channel.Write(ClassId.Threed, 0x3468, 0x0); + channel.Write(ClassId.Threed, 0x346C, 0x0); + channel.Write(ClassId.Threed, 0x3470, 0x0); + channel.Write(ClassId.Threed, 0x3474, 0x0); + channel.Write(ClassId.Threed, 0x3478, 0x0); + channel.Write(ClassId.Threed, 0x347C, 0x0); + channel.Write(ClassId.Threed, 0x3480, 0x0); + channel.Write(ClassId.Threed, 0x3484, 0x0); + channel.Write(ClassId.Threed, 0x3488, 0x0); + channel.Write(ClassId.Threed, 0x348C, 0x0); + channel.Write(ClassId.Threed, 0x3490, 0x0); + channel.Write(ClassId.Threed, 0x3494, 0x0); + channel.Write(ClassId.Threed, 0x3498, 0x0); + channel.Write(ClassId.Threed, 0x349C, 0x0); + channel.Write(ClassId.Threed, 0x34A0, 0x0); + channel.Write(ClassId.Threed, 0x34A4, 0x0); + channel.Write(ClassId.Threed, 0x34A8, 0x0); + channel.Write(ClassId.Threed, 0x34AC, 0x0); + channel.Write(ClassId.Threed, 0x34B0, 0x0); + channel.Write(ClassId.Threed, 0x34B4, 0x0); + channel.Write(ClassId.Threed, 0x34B8, 0x0); + channel.Write(ClassId.Threed, 0x34BC, 0x0); + channel.Write(ClassId.Threed, 0x34C0, 0x0); + channel.Write(ClassId.Threed, 0x34C4, 0x0); + channel.Write(ClassId.Threed, 0x34C8, 0x0); + channel.Write(ClassId.Threed, 0x34CC, 0x0); + channel.Write(ClassId.Threed, 0x34D0, 0x0); + channel.Write(ClassId.Threed, 0x34D4, 0x0); + channel.Write(ClassId.Threed, 0x34D8, 0x0); + channel.Write(ClassId.Threed, 0x34DC, 0x0); + channel.Write(ClassId.Threed, 0x34E0, 0x0); + channel.Write(ClassId.Threed, 0x34E4, 0x0); + channel.Write(ClassId.Threed, 0x34E8, 0x0); + channel.Write(ClassId.Threed, 0x34EC, 0x0); + channel.Write(ClassId.Threed, 0x34F0, 0x0); + channel.Write(ClassId.Threed, 0x34F4, 0x0); + channel.Write(ClassId.Threed, 0x34F8, 0x0); + channel.Write(ClassId.Threed, 0x34FC, 0x0); + channel.Write(ClassId.Threed, 0x3500, 0x0); + channel.Write(ClassId.Threed, 0x3504, 0x0); + channel.Write(ClassId.Threed, 0x3508, 0x0); + channel.Write(ClassId.Threed, 0x350C, 0x0); + channel.Write(ClassId.Threed, 0x3510, 0x0); + channel.Write(ClassId.Threed, 0x3514, 0x0); + channel.Write(ClassId.Threed, 0x3518, 0x0); + channel.Write(ClassId.Threed, 0x351C, 0x0); + channel.Write(ClassId.Threed, 0x3520, 0x0); + channel.Write(ClassId.Threed, 0x3524, 0x0); + channel.Write(ClassId.Threed, 0x3528, 0x0); + channel.Write(ClassId.Threed, 0x352C, 0x0); + channel.Write(ClassId.Threed, 0x3530, 0x0); + channel.Write(ClassId.Threed, 0x3534, 0x0); + channel.Write(ClassId.Threed, 0x3538, 0x0); + channel.Write(ClassId.Threed, 0x353C, 0x0); + channel.Write(ClassId.Threed, 0x3540, 0x0); + channel.Write(ClassId.Threed, 0x3544, 0x0); + channel.Write(ClassId.Threed, 0x3548, 0x0); + channel.Write(ClassId.Threed, 0x354C, 0x0); + channel.Write(ClassId.Threed, 0x3550, 0x0); + channel.Write(ClassId.Threed, 0x3554, 0x0); + channel.Write(ClassId.Threed, 0x3558, 0x0); + channel.Write(ClassId.Threed, 0x355C, 0x0); + channel.Write(ClassId.Threed, 0x3560, 0x0); + channel.Write(ClassId.Threed, 0x3564, 0x0); + channel.Write(ClassId.Threed, 0x3568, 0x0); + channel.Write(ClassId.Threed, 0x356C, 0x0); + channel.Write(ClassId.Threed, 0x3570, 0x0); + channel.Write(ClassId.Threed, 0x3574, 0x0); + channel.Write(ClassId.Threed, 0x3578, 0x0); + channel.Write(ClassId.Threed, 0x357C, 0x0); + channel.Write(ClassId.Threed, 0x3580, 0x0); + channel.Write(ClassId.Threed, 0x3584, 0x0); + channel.Write(ClassId.Threed, 0x3588, 0x0); + channel.Write(ClassId.Threed, 0x358C, 0x0); + channel.Write(ClassId.Threed, 0x3590, 0x0); + channel.Write(ClassId.Threed, 0x3594, 0x0); + channel.Write(ClassId.Threed, 0x3598, 0x0); + channel.Write(ClassId.Threed, 0x359C, 0x0); + channel.Write(ClassId.Threed, 0x35A0, 0x0); + channel.Write(ClassId.Threed, 0x35A4, 0x0); + channel.Write(ClassId.Threed, 0x35A8, 0x0); + channel.Write(ClassId.Threed, 0x35AC, 0x0); + channel.Write(ClassId.Threed, 0x35B0, 0x0); + channel.Write(ClassId.Threed, 0x35B4, 0x0); + channel.Write(ClassId.Threed, 0x35B8, 0x0); + channel.Write(ClassId.Threed, 0x35BC, 0x0); + channel.Write(ClassId.Threed, 0x35C0, 0x0); + channel.Write(ClassId.Threed, 0x35C4, 0x0); + channel.Write(ClassId.Threed, 0x35C8, 0x0); + channel.Write(ClassId.Threed, 0x35CC, 0x0); + channel.Write(ClassId.Threed, 0x35D0, 0x0); + channel.Write(ClassId.Threed, 0x35D4, 0x0); + channel.Write(ClassId.Threed, 0x35D8, 0x0); + channel.Write(ClassId.Threed, 0x35DC, 0x0); + channel.Write(ClassId.Threed, 0x35E0, 0x0); + channel.Write(ClassId.Threed, 0x35E4, 0x0); + channel.Write(ClassId.Threed, 0x35E8, 0x0); + channel.Write(ClassId.Threed, 0x35EC, 0x0); + channel.Write(ClassId.Threed, 0x35F0, 0x0); + channel.Write(ClassId.Threed, 0x35F4, 0x0); + channel.Write(ClassId.Threed, 0x35F8, 0x0); + channel.Write(ClassId.Threed, 0x35FC, 0x0); + channel.Write(ClassId.Threed, 0x30C, 0x1); + channel.Write(ClassId.Threed, 0x1944, 0x0); + channel.Write(ClassId.Threed, 0x1514, 0x0); + channel.Write(ClassId.Threed, 0xD68, 0xFFFF); + channel.Write(ClassId.Threed, 0x121C, 0xFAC6881); + channel.Write(ClassId.Threed, 0xFAC, 0x1); + channel.Write(ClassId.Threed, 0x1538, 0x1); + channel.Write(ClassId.Threed, 0xFE0, 0x0); + channel.Write(ClassId.Threed, 0xFE4, 0x0); + channel.Write(ClassId.Threed, 0xFE8, 0x14); + channel.Write(ClassId.Threed, 0xFEC, 0x40); + channel.Write(ClassId.Threed, 0xFF0, 0x0); + channel.Write(ClassId.Threed, 0x179C, 0x0); + channel.Write(ClassId.Threed, 0x1228, 0x400); + channel.Write(ClassId.Threed, 0x122C, 0x300); + channel.Write(ClassId.Threed, 0x1230, 0x10001); + channel.Write(ClassId.Threed, 0x7F8, 0x0); + channel.Write(ClassId.Threed, 0x1208, 0x0); + channel.Write(ClassId.Threed, 0x15B4, 0x1); + channel.Write(ClassId.Threed, 0x15CC, 0x0); + channel.Write(ClassId.Threed, 0x1534, 0x0); + channel.Write(ClassId.Threed, 0x754, 0x1); + channel.Write(ClassId.Threed, 0xFB0, 0x0); + channel.Write(ClassId.Threed, 0x15D0, 0x0); + channel.Write(ClassId.Threed, 0x11E0, 0x88888888); + channel.Write(ClassId.Threed, 0x11E4, 0x88888888); + channel.Write(ClassId.Threed, 0x11E8, 0x88888888); + channel.Write(ClassId.Threed, 0x11EC, 0x88888888); + channel.Write(ClassId.Threed, 0x153C, 0x0); + channel.Write(ClassId.Threed, 0x16B4, 0x3); + channel.Write(ClassId.Threed, 0xFA4, 0x1); + channel.Write(ClassId.Threed, 0xFBC, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC0, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC4, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC8, 0xFFFF); + channel.Write(ClassId.Threed, 0xFA8, 0xFFFF); + channel.Write(ClassId.Threed, 0xDF8, 0x0); + channel.Write(ClassId.Threed, 0xDFC, 0x0); + channel.Write(ClassId.Threed, 0x1948, 0x0); + channel.Write(ClassId.Threed, 0x1970, 0x1); + channel.Write(ClassId.Threed, 0x161C, 0x9F0); + channel.Write(ClassId.Threed, 0xDCC, 0x10); + channel.Write(ClassId.Threed, 0x15E4, 0x0); + channel.Write(ClassId.Threed, 0x1160, 0x25E00040); + channel.Write(ClassId.Threed, 0x1164, 0x25E00040); + channel.Write(ClassId.Threed, 0x1168, 0x25E00040); + channel.Write(ClassId.Threed, 0x116C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1170, 0x25E00040); + channel.Write(ClassId.Threed, 0x1174, 0x25E00040); + channel.Write(ClassId.Threed, 0x1178, 0x25E00040); + channel.Write(ClassId.Threed, 0x117C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1180, 0x25E00040); + channel.Write(ClassId.Threed, 0x1184, 0x25E00040); + channel.Write(ClassId.Threed, 0x1188, 0x25E00040); + channel.Write(ClassId.Threed, 0x118C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1190, 0x25E00040); + channel.Write(ClassId.Threed, 0x1194, 0x25E00040); + channel.Write(ClassId.Threed, 0x1198, 0x25E00040); + channel.Write(ClassId.Threed, 0x119C, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11AC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11BC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11CC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11DC, 0x25E00040); + channel.Write(ClassId.Threed, 0x1880, 0x0); + channel.Write(ClassId.Threed, 0x1884, 0x0); + channel.Write(ClassId.Threed, 0x1888, 0x0); + channel.Write(ClassId.Threed, 0x188C, 0x0); + channel.Write(ClassId.Threed, 0x1890, 0x0); + channel.Write(ClassId.Threed, 0x1894, 0x0); + channel.Write(ClassId.Threed, 0x1898, 0x0); + channel.Write(ClassId.Threed, 0x189C, 0x0); + channel.Write(ClassId.Threed, 0x18A0, 0x0); + channel.Write(ClassId.Threed, 0x18A4, 0x0); + channel.Write(ClassId.Threed, 0x18A8, 0x0); + channel.Write(ClassId.Threed, 0x18AC, 0x0); + channel.Write(ClassId.Threed, 0x18B0, 0x0); + channel.Write(ClassId.Threed, 0x18B4, 0x0); + channel.Write(ClassId.Threed, 0x18B8, 0x0); + channel.Write(ClassId.Threed, 0x18BC, 0x0); + channel.Write(ClassId.Threed, 0x18C0, 0x0); + channel.Write(ClassId.Threed, 0x18C4, 0x0); + channel.Write(ClassId.Threed, 0x18C8, 0x0); + channel.Write(ClassId.Threed, 0x18CC, 0x0); + channel.Write(ClassId.Threed, 0x18D0, 0x0); + channel.Write(ClassId.Threed, 0x18D4, 0x0); + channel.Write(ClassId.Threed, 0x18D8, 0x0); + channel.Write(ClassId.Threed, 0x18DC, 0x0); + channel.Write(ClassId.Threed, 0x18E0, 0x0); + channel.Write(ClassId.Threed, 0x18E4, 0x0); + channel.Write(ClassId.Threed, 0x18E8, 0x0); + channel.Write(ClassId.Threed, 0x18EC, 0x0); + channel.Write(ClassId.Threed, 0x18F0, 0x0); + channel.Write(ClassId.Threed, 0x18F4, 0x0); + channel.Write(ClassId.Threed, 0x18F8, 0x0); + channel.Write(ClassId.Threed, 0x18FC, 0x0); + channel.Write(ClassId.Threed, 0xF84, 0x0); + channel.Write(ClassId.Threed, 0xF88, 0x0); + channel.Write(ClassId.Threed, 0x17C8, 0x0); + channel.Write(ClassId.Threed, 0x17CC, 0x0); + channel.Write(ClassId.Threed, 0x17D0, 0xFF); + channel.Write(ClassId.Threed, 0x17D4, 0xFFFFFFFF); + channel.Write(ClassId.Threed, 0x17D8, 0x2); + channel.Write(ClassId.Threed, 0x17DC, 0x0); + channel.Write(ClassId.Threed, 0x15F4, 0x0); + channel.Write(ClassId.Threed, 0x15F8, 0x0); + channel.Write(ClassId.Threed, 0x1434, 0x0); + channel.Write(ClassId.Threed, 0x1438, 0x0); + channel.Write(ClassId.Threed, 0xD74, 0x0); + channel.Write(ClassId.Threed, 0x13A4, 0x0); + channel.Write(ClassId.Threed, 0x1318, 0x1); + channel.Write(ClassId.Threed, 0x1080, 0x0); + channel.Write(ClassId.Threed, 0x1084, 0x0); + channel.Write(ClassId.Threed, 0x1088, 0x1); + channel.Write(ClassId.Threed, 0x108C, 0x1); + channel.Write(ClassId.Threed, 0x1090, 0x0); + channel.Write(ClassId.Threed, 0x1094, 0x1); + channel.Write(ClassId.Threed, 0x1098, 0x0); + channel.Write(ClassId.Threed, 0x109C, 0x1); + channel.Write(ClassId.Threed, 0x10A0, 0x0); + channel.Write(ClassId.Threed, 0x10A4, 0x0); + channel.Write(ClassId.Threed, 0x1644, 0x0); + channel.Write(ClassId.Threed, 0x748, 0x0); + channel.Write(ClassId.Threed, 0xDE8, 0x0); + channel.Write(ClassId.Threed, 0x1648, 0x0); + channel.Write(ClassId.Threed, 0x12A4, 0x0); + channel.Write(ClassId.Threed, 0x1120, 0x0); + channel.Write(ClassId.Threed, 0x1124, 0x0); + channel.Write(ClassId.Threed, 0x1128, 0x0); + channel.Write(ClassId.Threed, 0x112C, 0x0); + channel.Write(ClassId.Threed, 0x1118, 0x0); + channel.Write(ClassId.Threed, 0x164C, 0x0); + channel.Write(ClassId.Threed, 0x1658, 0x0); + channel.Write(ClassId.Threed, 0x1910, 0x290); + channel.Write(ClassId.Threed, 0x1518, 0x0); + channel.Write(ClassId.Threed, 0x165C, 0x1); + channel.Write(ClassId.Threed, 0x1520, 0x0); + channel.Write(ClassId.Threed, 0x1604, 0x0); + channel.Write(ClassId.Threed, 0x1570, 0x0); + channel.Write(ClassId.Threed, 0x13B0, 0x3F800000); + channel.Write(ClassId.Threed, 0x13B4, 0x3F800000); + channel.Write(ClassId.Threed, 0x20C, 0x0); + channel.Write(ClassId.Threed, 0x1670, 0x30201000); + channel.Write(ClassId.Threed, 0x1674, 0x70605040); + channel.Write(ClassId.Threed, 0x1678, 0xB8A89888); + channel.Write(ClassId.Threed, 0x167C, 0xF8E8D8C8); + channel.Write(ClassId.Threed, 0x166C, 0x0); + channel.Write(ClassId.Threed, 0x1680, 0xFFFF00); + channel.Write(ClassId.Threed, 0x12D0, 0x3); + channel.Write(ClassId.Threed, 0x113C, 0x0); + channel.Write(ClassId.Threed, 0x12D4, 0x2); + channel.Write(ClassId.Threed, 0x1684, 0x0); + channel.Write(ClassId.Threed, 0x1688, 0x0); + channel.Write(ClassId.Threed, 0xDAC, 0x1B02); + channel.Write(ClassId.Threed, 0xDB0, 0x1B02); + channel.Write(ClassId.Threed, 0xDB4, 0x0); + channel.Write(ClassId.Threed, 0x168C, 0x0); + channel.Write(ClassId.Threed, 0x15BC, 0x0); + channel.Write(ClassId.Threed, 0x156C, 0x0); + channel.Write(ClassId.Threed, 0x187C, 0x0); + channel.Write(ClassId.Threed, 0x1110, 0x1); + channel.Write(ClassId.Threed, 0xDC0, 0x0); + channel.Write(ClassId.Threed, 0xDC4, 0x0); + channel.Write(ClassId.Threed, 0xDC8, 0x0); + channel.Write(ClassId.Threed, 0xF40, 0x0); + channel.Write(ClassId.Threed, 0xF44, 0x0); + channel.Write(ClassId.Threed, 0xF48, 0x0); + channel.Write(ClassId.Threed, 0xF4C, 0x0); + channel.Write(ClassId.Threed, 0xF50, 0x0); + channel.Write(ClassId.Threed, 0x1234, 0x0); + channel.Write(ClassId.Threed, 0x1690, 0x0); + channel.Write(ClassId.Threed, 0x790, 0x0); + channel.Write(ClassId.Threed, 0x794, 0x0); + channel.Write(ClassId.Threed, 0x798, 0x0); + channel.Write(ClassId.Threed, 0x79C, 0x0); + channel.Write(ClassId.Threed, 0x7A0, 0x0); + channel.Write(ClassId.Threed, 0x77C, 0x0); + channel.Write(ClassId.Threed, 0x1000, 0x10); + channel.Write(ClassId.Threed, 0x10FC, 0x0); + channel.Write(ClassId.Threed, 0x1290, 0x0); + channel.Write(ClassId.Threed, 0x218, 0x10); + channel.Write(ClassId.Threed, 0x12D8, 0x0); + channel.Write(ClassId.Threed, 0x12DC, 0x10); + channel.Write(ClassId.Threed, 0xD94, 0x1); + channel.Write(ClassId.Threed, 0x155C, 0x0); + channel.Write(ClassId.Threed, 0x1560, 0x0); + channel.Write(ClassId.Threed, 0x1564, 0xFFF); + channel.Write(ClassId.Threed, 0x1574, 0x0); + channel.Write(ClassId.Threed, 0x1578, 0x0); + channel.Write(ClassId.Threed, 0x157C, 0xFFFFF); + channel.Write(ClassId.Threed, 0x1354, 0x0); + channel.Write(ClassId.Threed, 0x1610, 0x12); + channel.Write(ClassId.Threed, 0x1608, 0x0); + channel.Write(ClassId.Threed, 0x160C, 0x0); + channel.Write(ClassId.Threed, 0x260C, 0x0); + channel.Write(ClassId.Threed, 0x7AC, 0x0); + channel.Write(ClassId.Threed, 0x162C, 0x3); + channel.Write(ClassId.Threed, 0x210, 0x0); + channel.Write(ClassId.Threed, 0x320, 0x0); + channel.Write(ClassId.Threed, 0x324, 0x3F800000); + channel.Write(ClassId.Threed, 0x328, 0x3F800000); + channel.Write(ClassId.Threed, 0x32C, 0x3F800000); + channel.Write(ClassId.Threed, 0x330, 0x3F800000); + channel.Write(ClassId.Threed, 0x334, 0x3F800000); + channel.Write(ClassId.Threed, 0x338, 0x3F800000); + channel.Write(ClassId.Threed, 0x750, 0x0); + channel.Write(ClassId.Threed, 0x760, 0x39291909); + channel.Write(ClassId.Threed, 0x764, 0x79695949); + channel.Write(ClassId.Threed, 0x768, 0xB9A99989); + channel.Write(ClassId.Threed, 0x76C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x770, 0x30201000); + channel.Write(ClassId.Threed, 0x774, 0x70605040); + channel.Write(ClassId.Threed, 0x778, 0x9080); + channel.Write(ClassId.Threed, 0x780, 0x39291909); + channel.Write(ClassId.Threed, 0x784, 0x79695949); + channel.Write(ClassId.Threed, 0x788, 0xB9A99989); + channel.Write(ClassId.Threed, 0x78C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x7D0, 0x30201000); + channel.Write(ClassId.Threed, 0x7D4, 0x70605040); + channel.Write(ClassId.Threed, 0x7D8, 0x9080); + channel.Write(ClassId.Threed, 0x1004, 0x0); + channel.Write(ClassId.Threed, 0x1240, 0x0); + channel.Write(ClassId.Threed, 0x1244, 0x0); + channel.Write(ClassId.Threed, 0x1248, 0x0); + channel.Write(ClassId.Threed, 0x124C, 0x0); + channel.Write(ClassId.Threed, 0x1250, 0x0); + channel.Write(ClassId.Threed, 0x1254, 0x0); + channel.Write(ClassId.Threed, 0x1258, 0x0); + channel.Write(ClassId.Threed, 0x125C, 0x0); + channel.Write(ClassId.Threed, 0x37C, 0x1); + channel.Write(ClassId.Threed, 0x740, 0x0); + channel.Write(ClassId.Threed, 0x1148, 0x0); + channel.Write(ClassId.Threed, 0xFB4, 0x0); + channel.Write(ClassId.Threed, 0xFB8, 0x2); + channel.Write(ClassId.Threed, 0x1130, 0x2); + channel.Write(ClassId.Threed, 0xFD4, 0x0); + channel.Write(ClassId.Threed, 0xFD8, 0x0); + channel.Write(ClassId.Threed, 0x1030, 0x20181008); + channel.Write(ClassId.Threed, 0x1034, 0x40383028); + channel.Write(ClassId.Threed, 0x1038, 0x60585048); + channel.Write(ClassId.Threed, 0x103C, 0x80787068); + channel.Write(ClassId.Threed, 0x744, 0x0); + channel.Write(ClassId.Threed, 0x2600, 0x0); + channel.Write(ClassId.Threed, 0x1918, 0x0); + channel.Write(ClassId.Threed, 0x191C, 0x900); + channel.Write(ClassId.Threed, 0x1920, 0x405); + channel.Write(ClassId.Threed, 0x1308, 0x1); + channel.Write(ClassId.Threed, 0x1924, 0x0); + channel.Write(ClassId.Threed, 0x13AC, 0x0); + channel.Write(ClassId.Threed, 0x192C, 0x1); + channel.Write(ClassId.Threed, 0x193C, 0x2C1C); + channel.Write(ClassId.Threed, 0xD7C, 0x0); + channel.Write(ClassId.Threed, 0xF8C, 0x0); + channel.Write(ClassId.Threed, 0x2C0, 0x1); + channel.Write(ClassId.Threed, 0x1510, 0x0); + channel.Write(ClassId.Threed, 0x1940, 0x0); + channel.Write(ClassId.Threed, 0xFF4, 0x0); + channel.Write(ClassId.Threed, 0xFF8, 0x0); + channel.Write(ClassId.Threed, 0x194C, 0x0); + channel.Write(ClassId.Threed, 0x1950, 0x0); + channel.Write(ClassId.Threed, 0x1968, 0x0); + channel.Write(ClassId.Threed, 0x1590, 0x3F); + channel.Write(ClassId.Threed, 0x7E8, 0x0); + channel.Write(ClassId.Threed, 0x7EC, 0x0); + channel.Write(ClassId.Threed, 0x7F0, 0x0); + channel.Write(ClassId.Threed, 0x7F4, 0x0); + channel.Write(ClassId.Threed, 0x196C, 0x11); + channel.Write(ClassId.Threed, 0x2E4, 0xB001); + channel.Write(ClassId.Threed, 0x36C, 0x0); + channel.Write(ClassId.Threed, 0x370, 0x0); + channel.Write(ClassId.Threed, 0x197C, 0x0); + channel.Write(ClassId.Threed, 0xFCC, 0x0); + channel.Write(ClassId.Threed, 0xFD0, 0x0); + channel.Write(ClassId.Threed, 0x2D8, 0x40); + channel.Write(ClassId.Threed, 0x1980, 0x80); + channel.Write(ClassId.Threed, 0x1504, 0x80); + channel.Write(ClassId.Threed, 0x1984, 0x0); + channel.Write(ClassId.Threed, 0xF60, 0x0); + channel.Write(ClassId.Threed, 0xF64, 0x400040); + channel.Write(ClassId.Threed, 0xF68, 0x2212); + channel.Write(ClassId.Threed, 0xF6C, 0x8080203); + channel.Write(ClassId.Threed, 0x1108, 0x8); + channel.Write(ClassId.Threed, 0xF70, 0x80001); + channel.Write(ClassId.Threed, 0xFFC, 0x0); + channel.Write(ClassId.Threed, 0x1134, 0x0); + channel.Write(ClassId.Threed, 0xF1C, 0x0); + channel.Write(ClassId.Threed, 0x11F8, 0x0); + channel.Write(ClassId.Threed, 0x1138, 0x1); + channel.Write(ClassId.Threed, 0x300, 0x1); + channel.Write(ClassId.Threed, 0x13A8, 0x0); + channel.Write(ClassId.Threed, 0x1224, 0x0); + channel.Write(ClassId.Threed, 0x12EC, 0x0); + channel.Write(ClassId.Threed, 0x1310, 0x0); + channel.Write(ClassId.Threed, 0x1314, 0x1); + channel.Write(ClassId.Threed, 0x1380, 0x0); + channel.Write(ClassId.Threed, 0x1384, 0x1); + channel.Write(ClassId.Threed, 0x1388, 0x1); + channel.Write(ClassId.Threed, 0x138C, 0x1); + channel.Write(ClassId.Threed, 0x1390, 0x1); + channel.Write(ClassId.Threed, 0x1394, 0x0); + channel.Write(ClassId.Threed, 0x139C, 0x0); + channel.Write(ClassId.Threed, 0x1398, 0x0); + channel.Write(ClassId.Threed, 0x1594, 0x0); + channel.Write(ClassId.Threed, 0x1598, 0x1); + channel.Write(ClassId.Threed, 0x159C, 0x1); + channel.Write(ClassId.Threed, 0x15A0, 0x1); + channel.Write(ClassId.Threed, 0x15A4, 0x1); + channel.Write(ClassId.Threed, 0xF54, 0x0); + channel.Write(ClassId.Threed, 0xF58, 0x0); + channel.Write(ClassId.Threed, 0xF5C, 0x0); + channel.Write(ClassId.Threed, 0x19BC, 0x0); + channel.Write(ClassId.Threed, 0xF9C, 0x0); + channel.Write(ClassId.Threed, 0xFA0, 0x0); + channel.Write(ClassId.Threed, 0x12CC, 0x0); + channel.Write(ClassId.Threed, 0x12E8, 0x0); + channel.Write(ClassId.Threed, 0x130C, 0x1); + channel.Write(ClassId.Threed, 0x1360, 0x0); + channel.Write(ClassId.Threed, 0x1364, 0x0); + channel.Write(ClassId.Threed, 0x1368, 0x0); + channel.Write(ClassId.Threed, 0x136C, 0x0); + channel.Write(ClassId.Threed, 0x1370, 0x0); + channel.Write(ClassId.Threed, 0x1374, 0x0); + channel.Write(ClassId.Threed, 0x1378, 0x0); + channel.Write(ClassId.Threed, 0x137C, 0x0); + channel.Write(ClassId.Threed, 0x133C, 0x1); + channel.Write(ClassId.Threed, 0x1340, 0x1); + channel.Write(ClassId.Threed, 0x1344, 0x2); + channel.Write(ClassId.Threed, 0x1348, 0x1); + channel.Write(ClassId.Threed, 0x134C, 0x1); + channel.Write(ClassId.Threed, 0x1350, 0x2); + channel.Write(ClassId.Threed, 0x1358, 0x1); + channel.Write(ClassId.Threed, 0x12E4, 0x0); + channel.Write(ClassId.Threed, 0x131C, 0x0); + channel.Write(ClassId.Threed, 0x1320, 0x0); + channel.Write(ClassId.Threed, 0x1324, 0x0); + channel.Write(ClassId.Threed, 0x1328, 0x0); + channel.Write(ClassId.Threed, 0x19C0, 0x0); + channel.Write(ClassId.Threed, 0x1140, 0x0); + channel.Write(ClassId.Threed, 0xDD0, 0x0); + channel.Write(ClassId.Threed, 0xDD4, 0x1); + channel.Write(ClassId.Threed, 0x2F4, 0x0); + channel.Write(ClassId.Threed, 0x19C4, 0x0); + channel.Write(ClassId.Threed, 0x19C8, 0x1500); + channel.Write(ClassId.Threed, 0x135C, 0x0); + channel.Write(ClassId.Threed, 0xF90, 0x0); + channel.Write(ClassId.Threed, 0x19E0, 0x1); + channel.Write(ClassId.Threed, 0x19E4, 0x1); + channel.Write(ClassId.Threed, 0x19E8, 0x1); + channel.Write(ClassId.Threed, 0x19EC, 0x1); + channel.Write(ClassId.Threed, 0x19F0, 0x1); + channel.Write(ClassId.Threed, 0x19F4, 0x1); + channel.Write(ClassId.Threed, 0x19F8, 0x1); + channel.Write(ClassId.Threed, 0x19FC, 0x1); + channel.Write(ClassId.Threed, 0x19CC, 0x1); + channel.Write(ClassId.Threed, 0x111C, 0x1); + channel.Write(ClassId.Threed, 0x15B8, 0x0); + channel.Write(ClassId.Threed, 0x1A00, 0x1111); + channel.Write(ClassId.Threed, 0x1A04, 0x0); + channel.Write(ClassId.Threed, 0x1A08, 0x0); + channel.Write(ClassId.Threed, 0x1A0C, 0x0); + channel.Write(ClassId.Threed, 0x1A10, 0x0); + channel.Write(ClassId.Threed, 0x1A14, 0x0); + channel.Write(ClassId.Threed, 0x1A18, 0x0); + channel.Write(ClassId.Threed, 0x1A1C, 0x0); + channel.Write(ClassId.Threed, 0xD6C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD70, 0xFFFF0000); + channel.Write(ClassId.Threed, 0x10F8, 0x1010); + channel.Write(ClassId.Threed, 0xD80, 0x0); + channel.Write(ClassId.Threed, 0xD84, 0x0); + channel.Write(ClassId.Threed, 0xD88, 0x0); + channel.Write(ClassId.Threed, 0xD8C, 0x0); + channel.Write(ClassId.Threed, 0xD90, 0x0); + channel.Write(ClassId.Threed, 0xDA0, 0x0); + channel.Write(ClassId.Threed, 0x7A4, 0x0); + channel.Write(ClassId.Threed, 0x7A8, 0x0); + channel.Write(ClassId.Threed, 0x1508, 0x80000000); + channel.Write(ClassId.Threed, 0x150C, 0x40000000); + channel.Write(ClassId.Threed, 0x1668, 0x0); + channel.Write(ClassId.Threed, 0x318, 0x8); + channel.Write(ClassId.Threed, 0x31C, 0x8); + channel.Write(ClassId.Threed, 0xD9C, 0x1); + channel.Write(ClassId.Threed, 0xF14, 0x0); + channel.Write(ClassId.Threed, 0x374, 0x0); + channel.Write(ClassId.Threed, 0x378, 0xC); + channel.Write(ClassId.Threed, 0x7DC, 0x0); + channel.Write(ClassId.Threed, 0x74C, 0x55); + channel.Write(ClassId.Threed, 0x1420, 0x3); + channel.Write(ClassId.Threed, 0x1008, 0x8); + channel.Write(ClassId.Threed, 0x100C, 0x40); + channel.Write(ClassId.Threed, 0x1010, 0x12C); + channel.Write(ClassId.Threed, 0xD60, 0x40); + channel.Write(ClassId.Threed, 0x1018, 0x20); + channel.Write(ClassId.Threed, 0x101C, 0x1); + channel.Write(ClassId.Threed, 0x1020, 0x20); + channel.Write(ClassId.Threed, 0x1024, 0x1); + channel.Write(ClassId.Threed, 0x1444, 0x0); + channel.Write(ClassId.Threed, 0x1448, 0x0); + channel.Write(ClassId.Threed, 0x144C, 0x0); + channel.Write(ClassId.Threed, 0x360, 0x20164010); + channel.Write(ClassId.Threed, 0x364, 0x20); + channel.Write(ClassId.Threed, 0x368, 0x0); + channel.Write(ClassId.Threed, 0xDA8, 0x30); + channel.Write(ClassId.Threed, 0xDE4, 0x0); + channel.Write(ClassId.Threed, 0x204, 0x6); + channel.Write(ClassId.Threed, 0x2D0, 0x3FFFFF); + channel.Write(ClassId.Threed, 0x1220, 0x5); + channel.Write(ClassId.Threed, 0xFDC, 0x0); + channel.Write(ClassId.Threed, 0xF98, 0x400008); + channel.Write(ClassId.Threed, 0x1284, 0x8000080); + channel.Write(ClassId.Threed, 0x1450, 0x400008); + channel.Write(ClassId.Threed, 0x1454, 0x8000080); + channel.Write(ClassId.Threed, 0x214, 0x0); + channel.Write(ClassId.Twod, 0x200, 0xCF); + channel.Write(ClassId.Twod, 0x204, 0x1); + channel.Write(ClassId.Twod, 0x208, 0x20); + channel.Write(ClassId.Twod, 0x20C, 0x1); + channel.Write(ClassId.Twod, 0x210, 0x0); + channel.Write(ClassId.Twod, 0x214, 0x80); + channel.Write(ClassId.Twod, 0x218, 0x100); + channel.Write(ClassId.Twod, 0x21C, 0x100); + channel.Write(ClassId.Twod, 0x220, 0x0); + channel.Write(ClassId.Twod, 0x224, 0x0); + channel.Write(ClassId.Twod, 0x230, 0xCF); + channel.Write(ClassId.Twod, 0x234, 0x1); + channel.Write(ClassId.Twod, 0x238, 0x20); + channel.Write(ClassId.Twod, 0x23C, 0x1); + channel.Write(ClassId.Twod, 0x244, 0x80); + channel.Write(ClassId.Twod, 0x248, 0x100); + channel.Write(ClassId.Twod, 0x24C, 0x100); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs new file mode 100644 index 00000000..9f16a280 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -0,0 +1,574 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvHostChannelDeviceFile : NvDeviceFile + { + private static readonly ConcurrentDictionary<ulong, Host1xContext> _host1xContextRegistry = new(); + + private const uint MaxModuleSyncpoint = 16; + + private uint _timeout; + private uint _submitTimeout; + private uint _timeslice; + + private readonly Switch _device; + + private readonly IVirtualMemoryManager _memory; + private readonly Host1xContext _host1xContext; + private readonly long _contextId; + + public GpuChannel Channel { get; } + + public enum ResourcePolicy + { + Device, + Channel + } + + protected static uint[] DeviceSyncpoints = new uint[MaxModuleSyncpoint]; + + protected uint[] ChannelSyncpoints; + + protected static ResourcePolicy ChannelResourcePolicy = ResourcePolicy.Device; + + private NvFence _channelSyncpoint; + + public NvHostChannelDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _device = context.Device; + _memory = memory; + _timeout = 3000; + _submitTimeout = 0; + _timeslice = 0; + _host1xContext = GetHost1XContext(context.Device.Gpu, owner); + _contextId = _host1xContext.Host1x.CreateContext(); + Channel = _device.Gpu.CreateChannel(); + + ChannelInitialization.InitializeState(Channel); + + ChannelSyncpoints = new uint[MaxModuleSyncpoint]; + + _channelSyncpoint.Id = _device.System.HostSyncpoint.AllocateSyncpoint(false); + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = Submit(arguments); + break; + case 0x02: + result = CallIoctlMethod<GetParameterArguments>(GetSyncpoint, arguments); + break; + case 0x03: + result = CallIoctlMethod<GetParameterArguments>(GetWaitBase, arguments); + break; + case 0x07: + result = CallIoctlMethod<uint>(SetSubmitTimeout, arguments); + break; + case 0x09: + result = MapCommandBuffer(arguments); + break; + case 0x0a: + result = UnmapCommandBuffer(arguments); + break; + } + } + else if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<int>(SetNvMapFd, arguments); + break; + case 0x03: + result = CallIoctlMethod<uint>(SetTimeout, arguments); + break; + case 0x08: + result = SubmitGpfifo(arguments); + break; + case 0x09: + result = CallIoctlMethod<AllocObjCtxArguments>(AllocObjCtx, arguments); + break; + case 0x0b: + result = CallIoctlMethod<ZcullBindArguments>(ZcullBind, arguments); + break; + case 0x0c: + result = CallIoctlMethod<SetErrorNotifierArguments>(SetErrorNotifier, arguments); + break; + case 0x0d: + result = CallIoctlMethod<NvChannelPriority>(SetPriority, arguments); + break; + case 0x18: + result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod<AllocGpfifoExArguments>(AllocGpfifoEx2, arguments); + break; + case 0x1d: + result = CallIoctlMethod<uint>(SetTimeslice, arguments); + break; + } + } + else if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod<ulong>(SetUserData, arguments); + break; + } + } + + return result; + } + + private NvInternalResult Submit(Span<byte> arguments) + { + SubmitArguments submitHeader = GetSpanAndSkip<SubmitArguments>(ref arguments, 1)[0]; + Span<CommandBuffer> commandBuffers = GetSpanAndSkip<CommandBuffer>(ref arguments, submitHeader.CmdBufsCount); + Span<Reloc> relocs = GetSpanAndSkip<Reloc>(ref arguments, submitHeader.RelocsCount); + Span<uint> relocShifts = GetSpanAndSkip<uint>(ref arguments, submitHeader.RelocsCount); + Span<SyncptIncr> syncptIncrs = GetSpanAndSkip<SyncptIncr>(ref arguments, submitHeader.SyncptIncrsCount); + Span<uint> fenceThresholds = GetSpanAndSkip<uint>(ref arguments, submitHeader.FencesCount); + + lock (_device) + { + for (int i = 0; i < syncptIncrs.Length; i++) + { + SyncptIncr syncptIncr = syncptIncrs[i]; + + uint id = syncptIncr.Id; + + fenceThresholds[i] = Context.Device.System.HostSyncpoint.IncrementSyncpointMax(id, syncptIncr.Incrs); + } + + foreach (CommandBuffer commandBuffer in commandBuffers) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem); + + var data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4); + + _host1xContext.Host1x.Submit(MemoryMarshal.Cast<byte, int>(data), _contextId); + } + } + + return NvInternalResult.Success; + } + + private Span<T> GetSpanAndSkip<T>(ref Span<byte> arguments, int count) where T : unmanaged + { + Span<T> output = MemoryMarshal.Cast<byte, T>(arguments).Slice(0, count); + + arguments = arguments.Slice(Unsafe.SizeOf<T>() * count); + + return output; + } + + private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments) + { + if (arguments.Parameter >= MaxModuleSyncpoint) + { + return NvInternalResult.InvalidInput; + } + + if (ChannelResourcePolicy == ResourcePolicy.Device) + { + arguments.Value = GetSyncpointDevice(_device.System.HostSyncpoint, arguments.Parameter, false); + } + else + { + arguments.Value = GetSyncpointChannel(arguments.Parameter, false); + } + + if (arguments.Value == 0) + { + return NvInternalResult.TryAgain; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetWaitBase(ref GetParameterArguments arguments) + { + arguments.Value = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetSubmitTimeout(ref uint submitTimeout) + { + _submitTimeout = submitTimeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult MapCommandBuffer(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0]; + Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress == 0) + { + ulong va = _host1xContext.MemoryAllocator.GetFreeAddress((ulong)map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize); + + if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + (uint)map.Size) <= uint.MaxValue) + { + _host1xContext.MemoryAllocator.AllocateRange(va, (uint)map.Size, freeAddressStartPosition); + _host1xContext.Smmu.Map(map.Address, va, (uint)map.Size, PteKind.Pitch); // FIXME: This should not use the GMMU. + map.DmaMapAddress = va; + } + else + { + map.DmaMapAddress = NvMemoryAllocator.PteUnmapped; + } + } + + commandBufferEntry.MapAddress = (int)map.DmaMapAddress; + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult UnmapCommandBuffer(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<MapCommandBufferArguments>(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast<byte, MapCommandBufferArguments>(arguments)[0]; + Span<CommandBufferHandle> commandBufferEntries = MemoryMarshal.Cast<byte, CommandBufferHandle>(arguments.Slice(headerSize)).Slice(0, commandBufferHeader.NumEntries); + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress != 0) + { + // FIXME: + // To make unmapping work, we need separate address space per channel. + // Right now NVDEC and VIC share the GPU address space which is not correct at all. + + // _host1xContext.MemoryAllocator.Free((ulong)map.DmaMapAddress, (uint)map.Size); + + // map.DmaMapAddress = 0; + } + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult SetNvMapFd(ref int nvMapFd) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeout(ref uint timeout) + { + _timeout = timeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SubmitGpfifo(Span<byte> arguments) + { + int headerSize = Unsafe.SizeOf<SubmitGpfifoArguments>(); + SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast<byte, SubmitGpfifoArguments>(arguments)[0]; + Span<ulong> gpfifoEntries = MemoryMarshal.Cast<byte, ulong>(arguments.Slice(headerSize)).Slice(0, gpfifoSubmissionHeader.NumEntries); + + return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries); + } + + private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullBind(ref ZcullBindArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetPriority(ref NvChannelPriority priority) + { + switch (priority) + { + case NvChannelPriority.Low: + _timeslice = 1300; // Timeslice low priority in micro-seconds + break; + case NvChannelPriority.Medium: + _timeslice = 2600; // Timeslice medium priority in micro-seconds + break; + case NvChannelPriority.High: + _timeslice = 5200; // Timeslice high priority in micro-seconds + break; + default: + return NvInternalResult.InvalidInput; + } + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeslice(ref uint timeslice) + { + if (timeslice < 1000 || timeslice > 50000) + { + return NvInternalResult.InvalidInput; + } + + _timeslice = timeslice; // in micro-seconds + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult SetUserData(ref ulong userData) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span<ulong> entries) + { + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + return NvInternalResult.InvalidInput; + } + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value)) + { + Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); + } + + header.Fence.Id = _channelSyncpoint.Id; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + uint incrementCount = header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) ? 2u : 0u; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + incrementCount += header.Fence.Value; + } + + header.Fence.Value = _device.System.HostSyncpoint.IncrementSyncpointMaxExt(header.Fence.Id, (int)incrementCount); + } + else + { + header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id); + } + + Channel.PushEntries(entries); + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement)) + { + Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); + } + + header.Flags = SubmitGpfifoFlags.None; + + _device.Gpu.GPFifo.SignalNewEntries(); + + return NvInternalResult.Success; + } + + public uint GetSyncpointChannel(uint index, bool isClientManaged) + { + if (ChannelSyncpoints[index] != 0) + { + return ChannelSyncpoints[index]; + } + + ChannelSyncpoints[index] = _device.System.HostSyncpoint.AllocateSyncpoint(isClientManaged); + + return ChannelSyncpoints[index]; + } + + public static uint GetSyncpointDevice(NvHostSyncpt syncpointManager, uint index, bool isClientManaged) + { + if (DeviceSyncpoints[index] != 0) + { + return DeviceSyncpoints[index]; + } + + DeviceSyncpoints[index] = syncpointManager.AllocateSyncpoint(isClientManaged); + + return DeviceSyncpoints[index]; + } + + private static int[] CreateWaitCommandBuffer(NvFence fence) + { + int[] commandBuffer = new int[4]; + + // SyncpointValue = fence.Value; + commandBuffer[0] = 0x2001001C; + commandBuffer[1] = (int)fence.Value; + + // SyncpointAction(fence.id, increment: false, switch_en: true); + commandBuffer[2] = 0x2001001D; + commandBuffer[3] = (((int)fence.Id << 8) | (0 << 0) | (1 << 4)); + + return commandBuffer; + } + + private int[] CreateIncrementCommandBuffer(ref NvFence fence, SubmitGpfifoFlags flags) + { + bool hasWfi = !flags.HasFlag(SubmitGpfifoFlags.SuppressWfi); + + int[] commandBuffer; + + int offset = 0; + + if (hasWfi) + { + commandBuffer = new int[8]; + + // WaitForInterrupt(handle) + commandBuffer[offset++] = 0x2001001E; + commandBuffer[offset++] = 0x0; + } + else + { + commandBuffer = new int[6]; + } + + // SyncpointValue = 0x0; + commandBuffer[offset++] = 0x2001001C; + commandBuffer[offset++] = 0x0; + + // Increment the syncpoint 2 times. (mitigate a hardware bug) + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + return commandBuffer; + } + + public override void Close() + { + _host1xContext.Host1x.DestroyContext(_contextId); + Channel.Dispose(); + + for (int i = 0; i < MaxModuleSyncpoint; i++) + { + if (ChannelSyncpoints[i] != 0) + { + _device.System.HostSyncpoint.ReleaseSyncpoint(ChannelSyncpoints[i]); + ChannelSyncpoints[i] = 0; + } + } + + _device.System.HostSyncpoint.ReleaseSyncpoint(_channelSyncpoint.Id); + _channelSyncpoint.Id = 0; + } + + private static Host1xContext GetHost1XContext(GpuContext gpu, ulong pid) + { + return _host1xContextRegistry.GetOrAdd(pid, (ulong key) => new Host1xContext(gpu, key)); + } + + public static void Destroy() + { + foreach (Host1xContext host1xContext in _host1xContextRegistry.Values) + { + host1xContext.Dispose(); + } + + _host1xContextRegistry.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs new file mode 100644 index 00000000..f33cc460 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs @@ -0,0 +1,105 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile + { + private KEvent _smExceptionBptIntReportEvent; + private KEvent _smExceptionBptPauseReportEvent; + private KEvent _errorNotifierEvent; + + private int _smExceptionBptIntReportEventHandle; + private int _smExceptionBptPauseReportEventHandle; + private int _errorNotifierEventHandle; + + public NvHostGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, memory, owner) + { + _smExceptionBptIntReportEvent = CreateEvent(context, out _smExceptionBptIntReportEventHandle); + _smExceptionBptPauseReportEvent = CreateEvent(context, out _smExceptionBptPauseReportEventHandle); + _errorNotifierEvent = CreateEvent(context, out _errorNotifierEventHandle); + } + + private static KEvent CreateEvent(ServiceCtx context, out int handle) + { + KEvent evnt = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + return evnt; + } + + public override NvInternalResult Ioctl2(NvIoctl command, Span<byte> arguments, Span<byte> inlineInBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x1b: + result = CallIoctlMethod<SubmitGpfifoArguments, ulong>(SubmitGpfifoEx, arguments, inlineInBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + switch (eventId) + { + case 0x1: + eventHandle = _smExceptionBptIntReportEventHandle; + break; + case 0x2: + eventHandle = _smExceptionBptPauseReportEventHandle; + break; + case 0x3: + eventHandle = _errorNotifierEventHandle; + break; + default: + eventHandle = 0; + break; + } + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span<ulong> inlineData) + { + return SubmitGpfifo(ref arguments, inlineData); + } + + public override void Close() + { + if (_smExceptionBptIntReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptIntReportEventHandle); + _smExceptionBptIntReportEventHandle = 0; + } + + if (_smExceptionBptPauseReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptPauseReportEventHandle); + _smExceptionBptPauseReportEventHandle = 0; + } + + if (_errorNotifierEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_errorNotifierEventHandle); + _errorNotifierEventHandle = 0; + } + + base.Close(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs new file mode 100644 index 00000000..8e5a1523 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocGpfifoExArguments + { + public uint NumEntries; + public uint NumJobs; + public uint Flags; + public NvFence Fence; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs new file mode 100644 index 00000000..fae91622 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocObjCtxArguments + { + public uint ClassNumber; + public uint Flags; + public ulong ObjectId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs new file mode 100644 index 00000000..425e665f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetParameterArguments + { + public uint Parameter; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs new file mode 100644 index 00000000..6a7e3da8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBufferHandle + { + public int MapHandle; + public int MapAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MapCommandBufferArguments + { + public int NumEntries; + public int DataAddress; // Ignored by the driver. + public bool AttachHostChDas; + public byte Padding1; + public short Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs new file mode 100644 index 00000000..8e2c6ca3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvChannel + { +#pragma warning disable CS0649 + public int Timeout; + public int SubmitTimeout; + public int Timeslice; +#pragma warning restore CS0649 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs new file mode 100644 index 00000000..4112a9fc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + enum NvChannelPriority : uint + { + Low = 50, + Medium = 100, + High = 150 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs new file mode 100644 index 00000000..1aba53ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SetErrorNotifierArguments + { + public ulong Offset; + public ulong Size; + public uint Mem; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs new file mode 100644 index 00000000..05c4280c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBuffer + { + public int Mem; + public uint Offset; + public int WordsCount; + } + + [StructLayout(LayoutKind.Sequential)] + struct Reloc + { + public int CmdbufMem; + public int CmdbufOffset; + public int Target; + public int TargetOffset; + } + + [StructLayout(LayoutKind.Sequential)] + struct SyncptIncr + { + public uint Id; + public uint Incrs; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } + + [StructLayout(LayoutKind.Sequential)] + struct SubmitArguments + { + public int CmdBufsCount; + public int RelocsCount; + public int SyncptIncrsCount; + public int FencesCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs new file mode 100644 index 00000000..a10abd4b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SubmitGpfifoArguments + { + public long Address; + public int NumEntries; + public SubmitGpfifoFlags Flags; + public NvFence Fence; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs new file mode 100644 index 00000000..d81fd386 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [Flags] + enum SubmitGpfifoFlags : uint + { + None, + FenceWait = 1 << 0, + FenceIncrement = 1 << 1, + HwFormat = 1 << 2, + SuppressWfi = 1 << 4, + IncrementWithValue = 1 << 8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs new file mode 100644 index 00000000..19a997f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullBindArguments + { + public ulong GpuVirtualAddress; + public uint Mode; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs new file mode 100644 index 00000000..f130c455 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs @@ -0,0 +1,540 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Memory; +using System; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + internal class NvHostCtrlDeviceFile : NvDeviceFile + { + public const int EventsCount = 64; + + private bool _isProductionMode; + private Switch _device; + private NvHostEvent[] _events; + + public NvHostCtrlDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting)) + { + _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is "" + } + else + { + _isProductionMode = true; + } + + _device = context.Device; + + _events = new NvHostEvent[EventsCount]; + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod<NvFence>(SyncptRead, arguments); + break; + case 0x15: + result = CallIoctlMethod<uint>(SyncptIncr, arguments); + break; + case 0x16: + result = CallIoctlMethod<SyncptWaitArguments>(SyncptWait, arguments); + break; + case 0x19: + result = CallIoctlMethod<SyncptWaitExArguments>(SyncptWaitEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod<NvFence>(SyncptReadMax, arguments); + break; + case 0x1b: + // As Marshal cannot handle unaligned arrays, we do everything by hand here. + GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments); + result = GetConfig(configArgument); + + if (result == NvInternalResult.Success) + { + configArgument.CopyTo(arguments); + } + break; + case 0x1c: + result = CallIoctlMethod<uint>(EventSignal, arguments); + break; + case 0x1d: + result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments); + break; + case 0x1e: + result = CallIoctlMethod<EventWaitArguments>(EventWaitAsync, arguments); + break; + case 0x1f: + result = CallIoctlMethod<uint>(EventRegister, arguments); + break; + case 0x20: + result = CallIoctlMethod<uint>(EventUnregister, arguments); + break; + case 0x21: + result = CallIoctlMethod<ulong>(EventKill, arguments); + break; + } + } + + return result; + } + + private int QueryEvent(uint eventId) + { + lock (_events) + { + uint eventSlot; + uint syncpointId; + + if ((eventId >> 28) == 1) + { + eventSlot = eventId & 0xFFFF; + syncpointId = (eventId >> 16) & 0xFFF; + } + else + { + eventSlot = eventId & 0xFF; + syncpointId = eventId >> 4; + } + + if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId) + { + return 0; + } + + return _events[eventSlot].EventHandle; + } + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = QueryEvent(eventId); + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SyncptRead(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: false); + } + + private NvInternalResult SyncptIncr(ref uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + _device.System.HostSyncpoint.Increment(id); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) + { + uint dummyValue = 0; + + return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) + { + return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptReadMax(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: true); + } + + private NvInternalResult GetConfig(GetConfigurationArguments arguments) + { + if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting)) + { + byte[] settingBuffer = new byte[0x101]; + + if (nvSetting is string stringValue) + { + if (stringValue.Length > 0x100) + { + Logger.Error?.Print(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + else if (nvSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nvSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nvSetting.GetType().Name); + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}"); + + arguments.Configuration = settingBuffer; + + return NvInternalResult.Success; + } + + // NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl. + //return NvInternalResult.NotAvailableInProduction; + return NvInternalResult.InvalidInput; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true); + } + + private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false); + } + + private NvInternalResult EventRegister(ref uint userEventId) + { + lock (_events) + { + NvInternalResult result = EventUnregister(ref userEventId); + + if (result == NvInternalResult.Success) + { + _events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System); + } + + return result; + } + + } + + private NvInternalResult EventUnregister(ref uint userEventId) + { + lock (_events) + { + if (userEventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + NvHostEvent hostEvent = _events[userEventId]; + + if (hostEvent == null) + { + return NvInternalResult.Success; + } + + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Cancelled || + hostEvent.State == NvHostEventState.Signaled) + { + _events[userEventId].CloseEvent(Context); + _events[userEventId] = null; + + return NvInternalResult.Success; + } + + return NvInternalResult.Busy; + } + } + + private NvInternalResult EventKill(ref ulong eventMask) + { + lock (_events) + { + NvInternalResult result = NvInternalResult.Success; + + for (uint eventId = 0; eventId < EventsCount; eventId++) + { + if ((eventMask & (1UL << (int)eventId)) != 0) + { + NvInternalResult tmp = EventUnregister(ref eventId); + + if (tmp != NvInternalResult.Success) + { + result = tmp; + } + } + } + + return result; + } + } + + private NvInternalResult EventSignal(ref uint userEventId) + { + uint eventId = userEventId & ushort.MaxValue; + + if (eventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + lock (_events) + { + NvHostEvent hostEvent = _events[eventId]; + + if (hostEvent == null) + { + return NvInternalResult.InvalidInput; + } + + hostEvent.Cancel(_device.Gpu); + + _device.System.HostSyncpoint.UpdateMin(hostEvent.Fence.Id); + + return NvInternalResult.Success; + } + } + + private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) + { + if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + if (max) + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id); + } + else + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id); + } + + return NvInternalResult.Success; + } + + private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd) + { + if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + // First try to check if the syncpoint is already expired on the CPU side + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id); + + return NvInternalResult.Success; + } + + // Try to invalidate the CPU cache and check for expiration again. + uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id); + + // Has the fence already expired? + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = newCachedSyncpointValue; + + return NvInternalResult.Success; + } + + // If the timeout is 0, directly return. + if (timeout == 0) + { + return NvInternalResult.TryAgain; + } + + // The syncpoint value isn't at the fence yet, we need to wait. + + if (!isWaitEventAsyncCmd) + { + value = 0; + } + + NvHostEvent hostEvent; + + NvInternalResult result; + + uint eventIndex; + + lock (_events) + { + if (isWaitEventAsyncCmd) + { + eventIndex = value; + + if (eventIndex >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + hostEvent = _events[eventIndex]; + } + else + { + hostEvent = GetFreeEventLocked(fence.Id, out eventIndex); + } + + if (hostEvent != null) + { + lock (hostEvent.Lock) + { + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Signaled || + hostEvent.State == NvHostEventState.Cancelled) + { + bool timedOut = hostEvent.Wait(_device.Gpu, fence); + + if (timedOut) + { + if (isWaitEventCmd) + { + value = ((fence.Id & 0xfff) << 16) | 0x10000000; + } + else + { + value = fence.Id << 4; + } + + value |= eventIndex; + + result = NvInternalResult.TryAgain; + } + else + { + value = fence.Value; + + return NvInternalResult.Success; + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + if (hostEvent != null) + { + Logger.Error?.Print(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu)); + } + + result = NvInternalResult.InvalidInput; + } + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvHostEvent GetFreeEventLocked(uint id, out uint eventIndex) + { + eventIndex = EventsCount; + + uint nullIndex = EventsCount; + + for (uint index = 0; index < EventsCount; index++) + { + NvHostEvent Event = _events[index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Available || + Event.State == NvHostEventState.Signaled || + Event.State == NvHostEventState.Cancelled) + { + eventIndex = index; + + if (Event.Fence.Id == id) + { + return Event; + } + } + } + else if (nullIndex == EventsCount) + { + nullIndex = index; + } + } + + if (nullIndex < EventsCount) + { + eventIndex = nullIndex; + + EventRegister(ref eventIndex); + + return _events[nullIndex]; + } + + if (eventIndex < EventsCount) + { + return _events[eventIndex]; + } + + return null; + } + + public override void Close() + { + Logger.Warning?.Print(LogClass.ServiceNv, "Closing channel"); + + lock (_events) + { + // If the device file need to be closed, cancel all user events and dispose events. + for (int i = 0; i < _events.Length; i++) + { + NvHostEvent evnt = _events[i]; + + if (evnt != null) + { + lock (evnt.Lock) + { + if (evnt.State == NvHostEventState.Waiting) + { + evnt.State = NvHostEventState.Cancelling; + + evnt.Cancel(_device.Gpu); + } + else if (evnt.State == NvHostEventState.Signaling) + { + // Wait at max 9ms if the guest app is trying to signal the event while closing it.. + int retryCount = 0; + do + { + if (retryCount++ > 9) + { + break; + } + + // TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work. + Thread.Sleep(1); + } while (evnt.State != NvHostEventState.Signaled); + } + + evnt.CloseEvent(Context); + + _events[i] = null; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs new file mode 100644 index 00000000..16f970e8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct EventWaitArguments + { + public NvFence Fence; + public int Timeout; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs new file mode 100644 index 00000000..3ee318a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + class GetConfigurationArguments + { + public string Domain; + public string Parameter; + public byte[] Configuration; + + public static GetConfigurationArguments FromSpan(Span<byte> span) + { + string domain = Encoding.ASCII.GetString(span.Slice(0, 0x41)); + string parameter = Encoding.ASCII.GetString(span.Slice(0x41, 0x41)); + + GetConfigurationArguments result = new GetConfigurationArguments + { + Domain = domain.Substring(0, domain.IndexOf('\0')), + Parameter = parameter.Substring(0, parameter.IndexOf('\0')), + Configuration = span.Slice(0x82, 0x101).ToArray() + }; + + return result; + } + + public void CopyTo(Span<byte> span) + { + Encoding.ASCII.GetBytes(Domain + '\0').CopyTo(span.Slice(0, 0x41)); + Encoding.ASCII.GetBytes(Parameter + '\0').CopyTo(span.Slice(0x41, 0x41)); + Configuration.CopyTo(span.Slice(0x82, 0x101)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs new file mode 100644 index 00000000..ac5512ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs @@ -0,0 +1,185 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Horizon.Common; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostEvent + { + public NvFence Fence; + public NvHostEventState State; + public KEvent Event; + public int EventHandle; + + private uint _eventId; + private NvHostSyncpt _syncpointManager; + private SyncpointWaiterHandle _waiterInformation; + + private NvFence _previousFailingFence; + private uint _failingCount; + + public readonly object Lock = new object(); + + /// <summary> + /// Max failing count until waiting on CPU. + /// FIXME: This seems enough for most of the cases, reduce if needed. + /// </summary> + private const uint FailingCountMax = 2; + + public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system) + { + Fence.Id = 0; + + State = NvHostEventState.Available; + + Event = new KEvent(system.KernelContext); + + if (KernelStatic.GetCurrentProcess().HandleTable.GenerateHandle(Event.ReadableEvent, out EventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + _eventId = eventId; + + _syncpointManager = syncpointManager; + + ResetFailingState(); + } + + private void ResetFailingState() + { + _previousFailingFence.Id = NvFence.InvalidSyncPointId; + _previousFailingFence.Value = 0; + _failingCount = 0; + } + + private void Signal() + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Signaling; + + if (oldState == NvHostEventState.Waiting) + { + Event.WritableEvent.Signal(); + } + + State = NvHostEventState.Signaled; + } + } + + private void GpuSignaled(SyncpointWaiterHandle waiterInformation) + { + lock (Lock) + { + // If the signal does not match our current waiter, + // then it is from a past fence and we should just ignore it. + if (waiterInformation != null && waiterInformation != _waiterInformation) + { + return; + } + + ResetFailingState(); + + Signal(); + } + } + + public void Cancel(GpuContext gpuContext) + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Cancelling; + + if (oldState == NvHostEventState.Waiting && _waiterInformation != null) + { + gpuContext.Synchronization.UnregisterCallback(Fence.Id, _waiterInformation); + _waiterInformation = null; + + if (_previousFailingFence.Id == Fence.Id && _previousFailingFence.Value == Fence.Value) + { + _failingCount++; + } + else + { + _failingCount = 1; + + _previousFailingFence = Fence; + } + } + + State = NvHostEventState.Cancelled; + + Event.WritableEvent.Clear(); + } + } + + public bool Wait(GpuContext gpuContext, NvFence fence) + { + lock (Lock) + { + // NOTE: nvservices code should always wait on the GPU side. + // If we do this, we may get an abort or undefined behaviour when the GPU processing thread is blocked for a long period (for example, during shader compilation). + // The reason for this is that the NVN code will try to wait until giving up. + // This is done by trying to wait and signal multiple times until aborting after you are past the timeout. + // As such, if it fails too many time, we enforce a wait on the CPU side indefinitely. + // This allows to keep GPU and CPU in sync when we are slow. + if (_failingCount == FailingCountMax) + { + Logger.Warning?.Print(LogClass.ServiceNv, "GPU processing thread is too slow, waiting on CPU..."); + + Fence.Wait(gpuContext, Timeout.InfiniteTimeSpan); + + ResetFailingState(); + + return false; + } + else + { + Fence = fence; + State = NvHostEventState.Waiting; + + _waiterInformation = gpuContext.Synchronization.RegisterCallbackOnSyncpoint(Fence.Id, Fence.Value, GpuSignaled); + + return true; + } + } + } + + public string DumpState(GpuContext gpuContext) + { + string res = $"\nNvHostEvent {_eventId}:\n"; + res += $"\tState: {State}\n"; + + if (State == NvHostEventState.Waiting) + { + res += "\tFence:\n"; + res += $"\t\tId : {Fence.Id}\n"; + res += $"\t\tThreshold : {Fence.Value}\n"; + res += $"\t\tCurrent Value : {gpuContext.Synchronization.GetSyncpointValue(Fence.Id)}\n"; + res += $"\t\tWaiter Valid : {_waiterInformation != null}\n"; + } + + return res; + } + + public void CloseEvent(ServiceCtx context) + { + if (EventHandle != 0) + { + context.Process.HandleTable.CloseHandle(EventHandle); + EventHandle = 0; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs new file mode 100644 index 00000000..c7b4bc9f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + enum NvHostEventState + { + Available = 0, + Waiting = 1, + Cancelling = 2, + Signaling = 3, + Signaled = 4, + Cancelled = 5 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs new file mode 100644 index 00000000..27dd1bd1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs @@ -0,0 +1,199 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostSyncpt + { + public const int VBlank0SyncpointId = 26; + public const int VBlank1SyncpointId = 27; + + private int[] _counterMin; + private int[] _counterMax; + private bool[] _clientManaged; + private bool[] _assigned; + + private Switch _device; + + private object _syncpointAllocatorLock = new object(); + + public NvHostSyncpt(Switch device) + { + _device = device; + _counterMin = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + _assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + + // Reserve VBLANK syncpoints + ReserveSyncpointLocked(VBlank0SyncpointId, true); + ReserveSyncpointLocked(VBlank1SyncpointId, true); + } + + private void ReserveSyncpointLocked(uint id, bool isClientManaged) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || _assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = true; + _clientManaged[id] = isClientManaged; + } + + public uint AllocateSyncpoint(bool isClientManaged) + { + lock (_syncpointAllocatorLock) + { + for (uint i = 1; i < SynchronizationManager.MaxHardwareSyncpoints; i++) + { + if (!_assigned[i]) + { + ReserveSyncpointLocked(i, isClientManaged); + return i; + } + } + } + + Logger.Error?.Print(LogClass.ServiceNv, "Cannot allocate a new syncpoint!"); + + return 0; + } + + public void ReleaseSyncpoint(uint id) + { + if (id == 0) + { + return; + } + + lock (_syncpointAllocatorLock) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || !_assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = false; + _clientManaged[id] = false; + + SetSyncpointMinEqualSyncpointMax(id); + } + } + + public void SetSyncpointMinEqualSyncpointMax(uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + int value = (int)ReadSyncpointValue(id); + + Interlocked.Exchange(ref _counterMax[id], value); + } + + public uint ReadSyncpointValue(uint id) + { + return UpdateMin(id); + } + + public uint ReadSyncpointMinValue(uint id) + { + return (uint)_counterMin[id]; + } + + public uint ReadSyncpointMaxValue(uint id) + { + return (uint)_counterMax[id]; + } + + private bool IsClientManaged(uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return false; + } + + return _clientManaged[id]; + } + + public void Increment(uint id) + { + if (IsClientManaged(id)) + { + IncrementSyncpointMax(id); + } + + IncrementSyncpointGPU(id); + } + + public uint UpdateMin(uint id) + { + uint newValue = _device.Gpu.Synchronization.GetSyncpointValue(id); + + Interlocked.Exchange(ref _counterMin[id], (int)newValue); + + return newValue; + } + + private void IncrementSyncpointGPU(uint id) + { + _device.Gpu.Synchronization.IncrementSyncpoint(id); + } + + public void IncrementSyncpointMin(uint id) + { + Interlocked.Increment(ref _counterMin[id]); + } + + public uint IncrementSyncpointMaxExt(uint id, int count) + { + if (count == 0) + { + return ReadSyncpointMaxValue(id); + } + + uint result = 0; + + for (int i = 0; i < count; i++) + { + result = IncrementSyncpointMax(id); + } + + return result; + } + + private uint IncrementSyncpointMax(uint id) + { + return (uint)Interlocked.Increment(ref _counterMax[id]); + } + + public uint IncrementSyncpointMax(uint id, uint incrs) + { + return (uint)Interlocked.Add(ref _counterMax[id], (int)incrs); + } + + public bool IsSyncpointExpired(uint id, uint threshold) + { + return MinCompare(id, _counterMin[id], _counterMax[id], (int)threshold); + } + + private bool MinCompare(uint id, int min, int max, int threshold) + { + int minDiff = min - threshold; + int maxDiff = max - threshold; + + if (IsClientManaged(id)) + { + return minDiff >= 0; + } + else + { + return (uint)maxDiff >= (uint)minDiff; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs new file mode 100644 index 00000000..cda97f18 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs @@ -0,0 +1,12 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitArguments + { + public NvFence Fence; + public int Timeout; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs new file mode 100644 index 00000000..f2279c3d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitExArguments + { + public SyncptWaitArguments Input; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs new file mode 100644 index 00000000..d6a8e29f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs @@ -0,0 +1,239 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu +{ + class NvHostCtrlGpuDeviceFile : NvDeviceFile + { + private static Stopwatch _pTimer = new Stopwatch(); + private static double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; + + private KEvent _errorEvent; + private KEvent _unknownEvent; + + public NvHostCtrlGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _errorEvent = new KEvent(context.Device.System.KernelContext); + _unknownEvent = new KEvent(context.Device.System.KernelContext); + } + + static NvHostCtrlGpuDeviceFile() + { + _pTimer.Start(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<ZcullGetCtxSizeArguments>(ZcullGetCtxSize, arguments); + break; + case 0x02: + result = CallIoctlMethod<ZcullGetInfoArguments>(ZcullGetInfo, arguments); + break; + case 0x03: + result = CallIoctlMethod<ZbcSetTableArguments>(ZbcSetTable, arguments); + break; + case 0x05: + result = CallIoctlMethod<GetCharacteristicsArguments>(GetCharacteristics, arguments); + break; + case 0x06: + result = CallIoctlMethod<GetTpcMasksArguments>(GetTpcMasks, arguments); + break; + case 0x14: + result = CallIoctlMethod<GetActiveSlotMaskArguments>(GetActiveSlotMask, arguments); + break; + case 0x1c: + result = CallIoctlMethod<GetGpuTimeArguments>(GetGpuTime, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span<byte> arguments, Span<byte> inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x05: + result = CallIoctlMethod<GetCharacteristicsArguments, GpuCharacteristics>(GetCharacteristics, arguments, inlineOutBuffer); + break; + case 0x06: + result = CallIoctlMethod<GetTpcMasksArguments, int>(GetTpcMasks, arguments, inlineOutBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + KEvent targetEvent = null; + + switch (eventId) + { + case 0x1: + targetEvent = _errorEvent; + break; + case 0x2: + targetEvent = _unknownEvent; + break; + } + + if (targetEvent != null) + { + if (Context.Process.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + public override void Close() { } + + private NvInternalResult ZcullGetCtxSize(ref ZcullGetCtxSizeArguments arguments) + { + arguments.Size = 1; + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullGetInfo(ref ZcullGetInfoArguments arguments) + { + arguments.WidthAlignPixels = 0x20; + arguments.HeightAlignPixels = 0x20; + arguments.PixelSquaresByAliquots = 0x400; + arguments.AliquotTotal = 0x800; + arguments.RegionByteMultiplier = 0x20; + arguments.RegionHeaderSize = 0x20; + arguments.SubregionHeaderSize = 0xc0; + arguments.SubregionWidthAlignPixels = 0x20; + arguments.SubregionHeightAlignPixels = 0x40; + arguments.SubregionCount = 0x10; + + return NvInternalResult.Success; + } + + private NvInternalResult ZbcSetTable(ref ZbcSetTableArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments) + { + return GetCharacteristics(ref arguments, ref arguments.Characteristics); + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments, ref GpuCharacteristics characteristics) + { + arguments.Header.BufferSize = 0xa0; + + characteristics.Arch = 0x120; + characteristics.Impl = 0xb; + characteristics.Rev = 0xa1; + characteristics.NumGpc = 0x1; + characteristics.L2CacheSize = 0x40000; + characteristics.OnBoardVideoMemorySize = 0x0; + characteristics.NumTpcPerGpc = 0x2; + characteristics.BusType = 0x20; + characteristics.BigPageSize = 0x20000; + characteristics.CompressionPageSize = 0x20000; + characteristics.PdeCoverageBitCount = 0x1b; + characteristics.AvailableBigPageSizes = 0x30000; + characteristics.GpcMask = 0x1; + characteristics.SmArchSmVersion = 0x503; + characteristics.SmArchSpaVersion = 0x503; + characteristics.SmArchWarpCount = 0x80; + characteristics.GpuVaBitCount = 0x28; + characteristics.Reserved = 0x0; + characteristics.Flags = 0x55; + characteristics.TwodClass = 0x902d; + characteristics.ThreedClass = 0xb197; + characteristics.ComputeClass = 0xb1c0; + characteristics.GpfifoClass = 0xb06f; + characteristics.InlineToMemoryClass = 0xa140; + characteristics.DmaCopyClass = 0xb0b5; + characteristics.MaxFbpsCount = 0x1; + characteristics.FbpEnMask = 0x0; + characteristics.MaxLtcPerFbp = 0x2; + characteristics.MaxLtsPerLtc = 0x1; + characteristics.MaxTexPerTpc = 0x0; + characteristics.MaxGpcCount = 0x1; + characteristics.RopL2EnMask0 = 0x21d70; + characteristics.RopL2EnMask1 = 0x0; + characteristics.ChipName = 0x6230326d67; + characteristics.GrCompbitStoreBaseHw = 0x0; + + arguments.Characteristics = characteristics; + + return NvInternalResult.Success; + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments) + { + return GetTpcMasks(ref arguments, ref arguments.TpcMask); + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int tpcMask) + { + if (arguments.MaskBufferSize != 0) + { + tpcMask = 3; + arguments.TpcMask = tpcMask; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.Slot = 0x07; + arguments.Mask = 0x01; + + return NvInternalResult.Success; + } + + private NvInternalResult GetGpuTime(ref GetGpuTimeArguments arguments) + { + arguments.Timestamp = GetPTimerNanoSeconds(); + + return NvInternalResult.Success; + } + + private static ulong GetPTimerNanoSeconds() + { + double ticks = _pTimer.ElapsedTicks; + + return (ulong)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs new file mode 100644 index 00000000..fd73be9e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetActiveSlotMaskArguments + { + public int Slot; + public int Mask; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs new file mode 100644 index 00000000..d6648178 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs @@ -0,0 +1,59 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GpuCharacteristics + { + public int Arch; + public int Impl; + public int Rev; + public int NumGpc; + public long L2CacheSize; + public long OnBoardVideoMemorySize; + public int NumTpcPerGpc; + public int BusType; + public int BigPageSize; + public int CompressionPageSize; + public int PdeCoverageBitCount; + public int AvailableBigPageSizes; + public int GpcMask; + public int SmArchSmVersion; + public int SmArchSpaVersion; + public int SmArchWarpCount; + public int GpuVaBitCount; + public int Reserved; + public long Flags; + public int TwodClass; + public int ThreedClass; + public int ComputeClass; + public int GpfifoClass; + public int InlineToMemoryClass; + public int DmaCopyClass; + public int MaxFbpsCount; + public int FbpEnMask; + public int MaxLtcPerFbp; + public int MaxLtsPerLtc; + public int MaxTexPerTpc; + public int MaxGpcCount; + public int RopL2EnMask0; + public int RopL2EnMask1; + public long ChipName; + public long GrCompbitStoreBaseHw; + } + + struct CharacteristicsHeader + { +#pragma warning disable CS0649 + public long BufferSize; + public long BufferAddress; +#pragma warning restore CS0649 + } + + [StructLayout(LayoutKind.Sequential)] + struct GetCharacteristicsArguments + { + public CharacteristicsHeader Header; + public GpuCharacteristics Characteristics; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs new file mode 100644 index 00000000..084ef71f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetGpuTimeArguments + { + public ulong Timestamp; + public ulong Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs new file mode 100644 index 00000000..16ef2d6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + + [StructLayout(LayoutKind.Sequential)] + struct GetTpcMasksArguments + { + public int MaskBufferSize; + public int Reserved; + public long MaskBufferAddress; + public int TpcMask; + public int Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs new file mode 100644 index 00000000..ed74cc26 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZbcColorArray + { + private uint element0; + private uint element1; + private uint element2; + private uint element3; + + public uint this[int index] + { + get + { + if (index == 0) + { + return element0; + } + else if (index == 1) + { + return element1; + } + else if (index == 2) + { + return element2; + } + else if (index == 2) + { + return element3; + } + + throw new IndexOutOfRangeException(); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + struct ZbcSetTableArguments + { + public ZbcColorArray ColorDs; + public ZbcColorArray ColorL2; + public uint Depth; + public uint Format; + public uint Type; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs new file mode 100644 index 00000000..1e668f86 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetCtxSizeArguments + { + public int Size; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs new file mode 100644 index 00000000..d0d152a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetInfoArguments + { + public int WidthAlignPixels; + public int HeightAlignPixels; + public int PixelSquaresByAliquots; + public int AliquotTotal; + public int RegionByteMultiplier; + public int RegionHeaderSize; + public int SubregionHeaderSize; + public int SubregionWidthAlignPixels; + public int SubregionHeightAlignPixels; + public int SubregionCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs new file mode 100644 index 00000000..fe302b98 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; +using System; +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu +{ + class NvHostDbgGpuDeviceFile : NvDeviceFile + { + public NvHostDbgGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs new file mode 100644 index 00000000..0a2087ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu +{ + class NvHostProfGpuDeviceFile : NvDeviceFile + { + public NvHostProfGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs new file mode 100644 index 00000000..9345baeb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + enum NvInternalResult : int + { + Success = 0, + OperationNotPermitted = -1, + NoEntry = -2, + Interrupted = -4, + IoError = -5, + DeviceNotFound = -6, + BadFileNumber = -9, + TryAgain = -11, + OutOfMemory = -12, + AccessDenied = -13, + BadAddress = -14, + Busy = -16, + NotADirectory = -20, + InvalidInput = -22, + FileTableOverflow = -23, + Unknown0x18 = -24, + NotSupported = -25, + FileTooBig = -27, + NoSpaceLeft = -28, + ReadOnlyAttribute = -30, + NotImplemented = -38, + InvalidState = -40, + Restart = -85, + InvalidAddress = -99, + TimedOut = -110, + Unknown0x72 = -114, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs new file mode 100644 index 00000000..a52b36a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -0,0 +1,272 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + internal class NvMapDeviceFile : NvDeviceFile + { + private const int FlagNotFreedYet = 1; + + private static NvMapIdDictionary _maps = new NvMapIdDictionary(); + + public NvMapDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvMapCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod<NvMapCreate>(Create, arguments); + break; + case 0x03: + result = CallIoctlMethod<NvMapFromId>(FromId, arguments); + break; + case 0x04: + result = CallIoctlMethod<NvMapAlloc>(Alloc, arguments); + break; + case 0x05: + result = CallIoctlMethod<NvMapFree>(Free, arguments); + break; + case 0x09: + result = CallIoctlMethod<NvMapParam>(Param, arguments); + break; + case 0x0e: + result = CallIoctlMethod<NvMapGetId>(GetId, arguments); + break; + case 0x02: + case 0x06: + case 0x07: + case 0x08: + case 0x0a: + case 0x0c: + case 0x0d: + case 0x0f: + case 0x10: + case 0x11: + result = NvInternalResult.NotSupported; + break; + } + } + + return result; + } + + private NvInternalResult Create(ref NvMapCreate arguments) + { + if (arguments.Size == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!"); + + return NvInternalResult.InvalidInput; + } + + int size = BitUtils.AlignUp(arguments.Size, (int)MemoryManager.PageSize); + + arguments.Handle = CreateHandleFromMap(new NvMapHandle(size)); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!"); + + return NvInternalResult.Success; + } + + private NvInternalResult FromId(ref NvMapFromId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Id); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + map.IncrementRefCount(); + + arguments.Handle = arguments.Id; + + return NvInternalResult.Success; + } + + private NvInternalResult Alloc(ref NvMapAlloc arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((arguments.Align & (arguments.Align - 1)) != 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((uint)arguments.Align < MemoryManager.PageSize) + { + arguments.Align = (int)MemoryManager.PageSize; + } + + NvInternalResult result = NvInternalResult.Success; + + if (!map.Allocated) + { + map.Allocated = true; + + map.Align = arguments.Align; + map.Kind = (byte)arguments.Kind; + + int size = BitUtils.AlignUp(map.Size, (int)MemoryManager.PageSize); + + ulong address = arguments.Address; + + if (address == 0) + { + // When the address is zero, we need to allocate + // our own backing memory for the NvMap. + // TODO: Is this allocation inside the transfer memory? + result = NvInternalResult.OutOfMemory; + } + + if (result == NvInternalResult.Success) + { + map.Size = size; + map.Address = address; + } + } + + return result; + } + + private NvInternalResult Free(ref NvMapFree arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if (DecrementMapRefCount(Owner, arguments.Handle)) + { + arguments.Address = map.Address; + arguments.Flags = 0; + } + else + { + arguments.Address = 0; + arguments.Flags = FlagNotFreedYet; + } + + arguments.Size = map.Size; + + return NvInternalResult.Success; + } + + private NvInternalResult Param(ref NvMapParam arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + switch (arguments.Param) + { + case NvMapHandleParam.Size: arguments.Result = map.Size; break; + case NvMapHandleParam.Align: arguments.Result = map.Align; break; + case NvMapHandleParam.Heap: arguments.Result = 0x40000000; break; + case NvMapHandleParam.Kind: arguments.Result = map.Kind; break; + case NvMapHandleParam.Compr: arguments.Result = 0; break; + + // Note: Base is not supported and returns an error. + // Any other value also returns an error. + default: return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetId(ref NvMapGetId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + arguments.Id = arguments.Handle; + + return NvInternalResult.Success; + } + + public override void Close() + { + // TODO: refcount NvMapDeviceFile instances and remove when closing + // _maps.TryRemove(GetOwner(), out _); + } + + private int CreateHandleFromMap(NvMapHandle map) + { + return _maps.Add(map); + } + + private static bool DeleteMapWithHandle(ulong pid, int handle) + { + return _maps.Delete(handle) != null; + } + + public static void IncrementMapRefCount(ulong pid, int handle) + { + GetMapFromHandle(pid, handle)?.IncrementRefCount(); + } + + public static bool DecrementMapRefCount(ulong pid, int handle) + { + NvMapHandle map = GetMapFromHandle(pid, handle); + + if (map == null) + { + return false; + } + + if (map.DecrementRefCount() <= 0) + { + DeleteMapWithHandle(pid, handle); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!"); + + return true; + } + else + { + return false; + } + } + + public static NvMapHandle GetMapFromHandle(ulong pid, int handle) + { + return _maps.Get(handle); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs new file mode 100644 index 00000000..2ec75fc9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapAlloc + { + public int Handle; + public int HeapMask; + public int Flags; + public int Align; + public long Kind; + public ulong Address; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs new file mode 100644 index 00000000..b47e4629 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapCreate + { + public int Size; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs new file mode 100644 index 00000000..34bcbc64 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFree + { + public int Handle; + public int Padding; + public ulong Address; + public int Size; + public int Flags; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs new file mode 100644 index 00000000..2e559534 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFromId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs new file mode 100644 index 00000000..fe574eea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapGetId + { + public int Id; + public int Handle; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs new file mode 100644 index 00000000..c97cee49 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs @@ -0,0 +1,40 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapHandle + { +#pragma warning disable CS0649 + public int Handle; + public int Id; +#pragma warning restore CS0649 + public int Size; + public int Align; + public int Kind; + public ulong Address; + public bool Allocated; + public ulong DmaMapAddress; + + private long _dupes; + + public NvMapHandle() + { + _dupes = 1; + } + + public NvMapHandle(int size) : this() + { + Size = size; + } + + public void IncrementRefCount() + { + Interlocked.Increment(ref _dupes); + } + + public long DecrementRefCount() + { + return Interlocked.Decrement(ref _dupes); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs new file mode 100644 index 00000000..61b73cba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + enum NvMapHandleParam : int + { + Size = 1, + Align = 2, + Base = 3, + Heap = 4, + Kind = 5, + Compr = 6 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs new file mode 100644 index 00000000..c4733e94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapIdDictionary + { + private readonly ConcurrentDictionary<int, NvMapHandle> _nvmapHandles; + private int _id; + + public ICollection<NvMapHandle> Values => _nvmapHandles.Values; + + public NvMapIdDictionary() + { + _nvmapHandles = new ConcurrentDictionary<int, NvMapHandle>(); + } + + public int Add(NvMapHandle handle) + { + int id = Interlocked.Add(ref _id, 4); + + if (id != 0 && _nvmapHandles.TryAdd(id, handle)) + { + return id; + } + + throw new InvalidOperationException("NvMap ID overflow."); + } + + public NvMapHandle Get(int id) + { + if (_nvmapHandles.TryGetValue(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public NvMapHandle Delete(int id) + { + if (_nvmapHandles.TryRemove(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public ICollection<NvMapHandle> Clear() + { + ICollection<NvMapHandle> values = _nvmapHandles.Values; + + _nvmapHandles.Clear(); + + return values; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs new file mode 100644 index 00000000..de5bab77 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapParam + { + public int Handle; + public NvMapHandleParam Param; + public int Result; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs new file mode 100644 index 00000000..05858694 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [StructLayout(LayoutKind.Sequential)] + struct NvIoctl + { + public const int NvHostCustomMagic = 0x00; + public const int NvMapCustomMagic = 0x01; + public const int NvGpuAsMagic = 0x41; + public const int NvGpuMagic = 0x47; + public const int NvHostMagic = 0x48; + + private const int NumberBits = 8; + private const int TypeBits = 8; + private const int SizeBits = 14; + private const int DirectionBits = 2; + + private const int NumberShift = 0; + private const int TypeShift = NumberShift + NumberBits; + private const int SizeShift = TypeShift + TypeBits; + private const int DirectionShift = SizeShift + SizeBits; + + private const int NumberMask = (1 << NumberBits) - 1; + private const int TypeMask = (1 << TypeBits) - 1; + private const int SizeMask = (1 << SizeBits) - 1; + private const int DirectionMask = (1 << DirectionBits) - 1; + + [Flags] + public enum Direction : uint + { + None = 0, + Read = 1, + Write = 2, + } + + public uint RawValue; + + public uint Number => (RawValue >> NumberShift) & NumberMask; + public uint Type => (RawValue >> TypeShift) & TypeMask; + public uint Size => (RawValue >> SizeShift) & SizeMask; + public Direction DirectionValue => (Direction)((RawValue >> DirectionShift) & DirectionMask); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs new file mode 100644 index 00000000..341b5e57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs @@ -0,0 +1,310 @@ +using Ryujinx.Common.Collections; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class NvMemoryAllocator + { + private const ulong AddressSpaceSize = 1UL << 40; + + private const ulong DefaultStart = 1UL << 32; + private const ulong InvalidAddress = 0; + + private const ulong PageSize = MemoryManager.PageSize; + private const ulong PageMask = MemoryManager.PageMask; + + public const ulong PteUnmapped = MemoryManager.PteUnmapped; + + // Key --> Start Address of Region + // Value --> End Address of Region + private readonly TreeDictionary<ulong, ulong> _tree = new TreeDictionary<ulong, ulong>(); + + private readonly Dictionary<ulong, LinkedListNode<ulong>> _dictionary = new Dictionary<ulong, LinkedListNode<ulong>>(); + private readonly LinkedList<ulong> _list = new LinkedList<ulong>(); + + public NvMemoryAllocator() + { + _tree.Add(PageSize, AddressSpaceSize); + LinkedListNode<ulong> node = _list.AddFirst(PageSize); + _dictionary[PageSize] = node; + } + + /// <summary> + /// Marks a range of memory as consumed by removing it from the tree. + /// This function will split memory regions if there is available space. + /// </summary> + /// <param name="va">Virtual address at which to allocate</param> + /// <param name="size">Size of the allocation in bytes</param> + /// <param name="referenceAddress">Reference to the address of memory where the allocation can take place</param> + #region Memory Allocation + public void AllocateRange(ulong va, ulong size, ulong referenceAddress = InvalidAddress) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Allocating range from 0x{va:X} to 0x{(va + size):X}."); + if (referenceAddress != InvalidAddress) + { + ulong endAddress = va + size; + ulong referenceEndAddress = _tree.Get(referenceAddress); + if (va >= referenceAddress) + { + // Need Left Node + if (va > referenceAddress) + { + ulong leftEndAddress = va; + + // Overwrite existing block with its new smaller range. + _tree.Add(referenceAddress, leftEndAddress); + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{referenceAddress:X} to 0x{leftEndAddress:X}."); + } + else + { + // We need to get rid of the large chunk. + _tree.Remove(referenceAddress); + } + + ulong rightSize = referenceEndAddress - endAddress; + // If leftover space, create a right node. + if (rightSize > 0) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{endAddress:X} to 0x{referenceEndAddress:X}."); + _tree.Add(endAddress, referenceEndAddress); + + LinkedListNode<ulong> node = _list.AddAfter(_dictionary[referenceAddress], endAddress); + _dictionary[endAddress] = node; + } + + if (va == referenceAddress) + { + _list.Remove(_dictionary[referenceAddress]); + _dictionary.Remove(referenceAddress); + } + } + } + } + } + + /// <summary> + /// Marks a range of memory as free by adding it to the tree. + /// This function will automatically compact the tree when it determines there are multiple ranges of free memory adjacent to each other. + /// </summary> + /// <param name="va">Virtual address at which to deallocate</param> + /// <param name="size">Size of the allocation in bytes</param> + public void DeallocateRange(ulong va, ulong size) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocating address range from 0x{va:X} to 0x{(va + size):X}."); + + ulong freeAddressStartPosition = _tree.Floor(va); + if (freeAddressStartPosition != InvalidAddress) + { + LinkedListNode<ulong> node = _dictionary[freeAddressStartPosition]; + ulong targetPrevAddress = _dictionary[freeAddressStartPosition].Previous != null ? _dictionary[_dictionary[freeAddressStartPosition].Previous.Value].Value : InvalidAddress; + ulong targetNextAddress = _dictionary[freeAddressStartPosition].Next != null ? _dictionary[_dictionary[freeAddressStartPosition].Next.Value].Value : InvalidAddress; + ulong expandedStart = va; + ulong expandedEnd = va + size; + + while (targetPrevAddress != InvalidAddress) + { + ulong prevAddress = targetPrevAddress; + ulong prevEndAddress = _tree.Get(targetPrevAddress); + if (prevEndAddress >= expandedStart) + { + expandedStart = targetPrevAddress; + LinkedListNode<ulong> prevPtr = _dictionary[prevAddress]; + if (prevPtr.Previous != null) + { + targetPrevAddress = prevPtr.Previous.Value; + } + else + { + targetPrevAddress = InvalidAddress; + } + node = node.Previous; + _tree.Remove(prevAddress); + _list.Remove(_dictionary[prevAddress]); + _dictionary.Remove(prevAddress); + } + else + { + break; + } + } + + while (targetNextAddress != InvalidAddress) + { + ulong nextAddress = targetNextAddress; + ulong nextEndAddress = _tree.Get(targetNextAddress); + if (nextAddress <= expandedEnd) + { + expandedEnd = Math.Max(expandedEnd, nextEndAddress); + LinkedListNode<ulong> nextPtr = _dictionary[nextAddress]; + if (nextPtr.Next != null) + { + targetNextAddress = nextPtr.Next.Value; + } + else + { + targetNextAddress = InvalidAddress; + } + _tree.Remove(nextAddress); + _list.Remove(_dictionary[nextAddress]); + _dictionary.Remove(nextAddress); + } + else + { + break; + } + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocation resulted in new free range from 0x{expandedStart:X} to 0x{expandedEnd:X}."); + + _tree.Add(expandedStart, expandedEnd); + LinkedListNode<ulong> nodePtr = _list.AddAfter(node, expandedStart); + _dictionary[expandedStart] = nodePtr; + } + } + } + + /// <summary> + /// Gets the address of an unused (free) region of the specified size. + /// </summary> + /// <param name="size">Size of the region in bytes</param> + /// <param name="freeAddressStartPosition">Position at which memory can be allocated</param> + /// <param name="alignment">Required alignment of the region address in bytes</param> + /// <param name="start">Start address of the search on the address space</param> + /// <returns>GPU virtual address of the allocation, or an all ones mask in case of failure</returns> + public ulong GetFreeAddress(ulong size, out ulong freeAddressStartPosition, ulong alignment = 1, ulong start = DefaultStart) + { + // Note: Address 0 is not considered valid by the driver, + // when 0 is returned it's considered a mapping error. + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Searching for a free address @ 0x{start:X} of size 0x{size:X}."); + ulong address = start; + + if (alignment == 0) + { + alignment = 1; + } + + alignment = (alignment + PageMask) & ~PageMask; + if (address < AddressSpaceSize) + { + bool reachedEndOfAddresses = false; + ulong targetAddress; + if (start == DefaultStart) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to start of the last available range: 0x{_list.Last.Value:X}."); + targetAddress = _list.Last.Value; + } + else + { + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to floor of 0x{address:X}; resulted in 0x{targetAddress:X}."); + if (targetAddress == InvalidAddress) + { + targetAddress = _tree.Ceiling(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address was invalid, set to ceiling of 0x{address:X}; resulted in 0x{targetAddress:X}"); + } + } + while (address < AddressSpaceSize) + { + if (targetAddress != InvalidAddress) + { + if (address >= targetAddress) + { + if (address + size <= _tree.Get(targetAddress)) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Found a suitable free address range from 0x{targetAddress:X} to 0x{_tree.Get(targetAddress):X} for 0x{address:X}."); + freeAddressStartPosition = targetAddress; + return address; + } + else + { + Logger.Debug?.Print(LogClass.ServiceNv, "Address requirements exceeded the available space in the target range."); + LinkedListNode<ulong> nextPtr = _dictionary[targetAddress]; + if (nextPtr.Next != null) + { + targetAddress = nextPtr.Next.Value; + Logger.Debug?.Print(LogClass.ServiceNv, $"Moved search to successor range starting at 0x{targetAddress:X}."); + } + else + { + if (reachedEndOfAddresses) + { + Logger.Debug?.Print(LogClass.ServiceNv, "Exiting loop, a full pass has already been completed w/ no suitable free address range."); + break; + } + else + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Reached the end of the available free ranges, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + } + else + { + address += PageSize * (targetAddress / PageSize - (address / PageSize)); + + ulong remainder = address % alignment; + + if (remainder != 0) + { + address = (address - remainder) + alignment; + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Reset and aligned address to {address:X}."); + + if (address + size > AddressSpaceSize && !reachedEndOfAddresses) + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Address requirements exceeded the capacity of available address space, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + else + { + break; + } + } + } + Logger.Debug?.Print(LogClass.ServiceNv, $"No suitable address range found; returning: 0x{InvalidAddress:X}."); + freeAddressStartPosition = InvalidAddress; + } + + return PteUnmapped; + } + + /// <summary> + /// Checks if a given memory region is mapped or reserved. + /// </summary> + /// <param name="gpuVa">GPU virtual address of the page</param> + /// <param name="size">Size of the allocation in bytes</param> + /// <param name="freeAddressStartPosition">Nearest lower address that memory can be allocated</param> + /// <returns>True if the page is mapped or reserved, false otherwise</returns> + public bool IsRegionInUse(ulong gpuVa, ulong size, out ulong freeAddressStartPosition) + { + lock (_tree) + { + ulong floorAddress = _tree.Floor(gpuVa); + freeAddressStartPosition = floorAddress; + if (floorAddress != InvalidAddress) + { + return !(gpuVa >= floorAddress && ((gpuVa + size) <= _tree.Get(floorAddress))); + } + } + return true; + } + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs new file mode 100644 index 00000000..664610a4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -0,0 +1,41 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + internal struct NvFence + { + public const uint InvalidSyncPointId = uint.MaxValue; + + public uint Id; + public uint Value; + + public bool IsValid() + { + return Id != InvalidSyncPointId; + } + + public void UpdateValue(NvHostSyncpt hostSyncpt) + { + Value = hostSyncpt.ReadSyncpointValue(Id); + } + + public void Increment(GpuContext gpuContext) + { + Value = gpuContext.Synchronization.IncrementSyncpoint(Id); + } + + public bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + if (IsValid()) + { + return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout); + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs new file mode 100644 index 00000000..9404c18c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs @@ -0,0 +1,55 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvIoctlNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public NvIoctl Command { get; } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command) + : this(context, deviceFile, command, "The ioctl is not implemented.") + { } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + Command = command; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Ioctl (0x{Command.RawValue:x8})"); + sb.AppendLine($"\tNumber: 0x{Command.Number:x8}"); + sb.AppendLine($"\tType: 0x{Command.Type:x8}"); + sb.AppendLine($"\tSize: 0x{Command.Size:x8}"); + sb.AppendLine($"\tDirection: {Command.DirectionValue}"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs new file mode 100644 index 00000000..b7a72eba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvQueryEventNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public uint EventId { get; } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId) + : this(context, deviceFile, eventId, "This query event is not implemented.") + { } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + EventId = eventId; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Event ID: (0x{EventId:x8})"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs new file mode 100644 index 00000000..1c9cae8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + enum NvResult : uint + { + Success = 0, + NotImplemented = 1, + NotSupported = 2, + NotInitialized = 3, + InvalidParameter = 4, + Timeout = 5, + InsufficientMemory = 6, + ReadOnlyAttribute = 7, + InvalidState = 8, + InvalidAddress = 9, + InvalidSize = 10, + InvalidValue = 11, + AlreadyAllocated = 13, + Busy = 14, + ResourceError = 15, + CountMismatch = 16, + SharedMemoryTooSmall = 0x1000, + FileOperationFailed = 0x30003, + DirectoryOperationFailed = 0x30004, + NotAvailableInProduction = 0x30006, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + FileNotFound = 0x30013, + ModuleNotPresent = 0xA000E, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs new file mode 100644 index 00000000..d5c35265 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct NvStatus + { + public uint MemoryValue1; + public uint MemoryValue2; + public uint MemoryValue3; + public uint MemoryValue4; + public long Padding1; + public long Padding2; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs new file mode 100644 index 00000000..89fe0c3a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + [Service("olsc:u")] // 10.0.0+ + class IOlscServiceForApplication : IpcService + { + private bool _initialized; + private Dictionary<UserId, bool> _saveDataBackupSettingDatabase; + + public IOlscServiceForApplication(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(pid) + public ResultCode Initialize(ServiceCtx context) + { + // NOTE: Service call arp:r GetApplicationInstanceUnregistrationNotifier with the pid and initialize some internal struct. + // Since we will not support online savedata backup, it's fine to stub it for now. + + _saveDataBackupSettingDatabase = new Dictionary<UserId, bool>(); + + _initialized = true; + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // GetSaveDataBackupSetting(nn::account::Uid) -> u8 + public ResultCode GetSaveDataBackupSetting(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (!_initialized) + { + return ResultCode.NotInitialized; + } + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + if (_saveDataBackupSettingDatabase.TryGetValue(userId, out bool enabled) && enabled) + { + context.ResponseData.Write((byte)1); // TODO: Determine value. + } + else + { + context.ResponseData.Write((byte)2); // TODO: Determine value. + } + + // NOTE: Since we will not support online savedata backup, it's fine to stub it for now. + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId }); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetSaveDataBackupSettingEnabled(nn::account::Uid, bool) + public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context) + { + bool saveDataBackupSettingEnabled = context.RequestData.ReadUInt64() != 0; + UserId userId = context.RequestData.ReadStruct<UserId>(); + + if (!_initialized) + { + return ResultCode.NotInitialized; + } + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + _saveDataBackupSettingDatabase[userId] = saveDataBackupSettingEnabled; + + // NOTE: Since we will not support online savedata backup, it's fine to stub it for now. + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId, saveDataBackupSettingEnabled }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs new file mode 100644 index 00000000..52f74da9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + [Service("olsc:s")] // 4.0.0+ + class IOlscServiceForSystemService : IpcService + { + public IOlscServiceForSystemService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs new file mode 100644 index 00000000..141d1ae9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + enum ResultCode + { + ModuleId = 179, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (100 << ErrorCodeShift) | ModuleId, + NotInitialized = (101 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs b/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs new file mode 100644 index 00000000..67b82e42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ovln +{ + [Service("ovln:rcv")] + class IReceiverService : IpcService + { + public IReceiverService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs b/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs new file mode 100644 index 00000000..70c860e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ovln +{ + [Service("ovln:snd")] + class ISenderService : IpcService + { + public ISenderService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs new file mode 100644 index 00000000..9c6387e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie:log")] + class ILogManager : IpcService + { + public ILogManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs new file mode 100644 index 00000000..f189dc8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs new file mode 100644 index 00000000..990aef09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs @@ -0,0 +1,40 @@ +using Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory; + +namespace Ryujinx.HLE.HOS.Services.Pctl +{ + [Service("pctl", 0x303)] + [Service("pctl:a", 0x83BE)] + [Service("pctl:r", 0x8040)] + [Service("pctl:s", 0x838E)] + class IParentalControlServiceFactory : IpcService + { + private int _permissionFlag; + + public IParentalControlServiceFactory(ServiceCtx context, int permissionFlag) + { + _permissionFlag = permissionFlag; + } + + [CommandCmif(0)] + // CreateService(u64, pid) -> object<nn::pctl::detail::ipc::IParentalControlService> + public ResultCode CreateService(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + MakeObject(context, new IParentalControlService(context, pid, true, _permissionFlag)); + + return ResultCode.Success; + } + + [CommandCmif(1)] // 4.0.0+ + // CreateServiceWithoutInitialize(u64, pid) -> object<nn::pctl::detail::ipc::IParentalControlService> + public ResultCode CreateServiceWithoutInitialize(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + MakeObject(context, new IParentalControlService(context, pid, false, _permissionFlag)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs new file mode 100644 index 00000000..594ee4e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -0,0 +1,259 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using System; + +using static LibHac.Ns.ApplicationControlProperty; + +namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory +{ + class IParentalControlService : IpcService + { + private ulong _pid; + private int _permissionFlag; + private ulong _titleId; + private ParentalControlFlagValue _parentalControlFlag; + private int[] _ratingAge; + +#pragma warning disable CS0414 + // TODO: Find where they are set. + private bool _restrictionEnabled = false; + private bool _featuresRestriction = false; + private bool _freeCommunicationEnabled = false; + private bool _stereoVisionRestrictionConfigurable = true; + private bool _stereoVisionRestriction = false; +#pragma warning restore CS0414 + + public IParentalControlService(ServiceCtx context, ulong pid, bool withInitialize, int permissionFlag) + { + _pid = pid; + _permissionFlag = permissionFlag; + + if (withInitialize) + { + Initialize(context); + } + } + + [CommandCmif(1)] // 4.0.0+ + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + if ((_permissionFlag & 0x8001) == 0) + { + return ResultCode.PermissionDenied; + } + + ResultCode resultCode = ResultCode.InvalidPid; + + if (_pid != 0) + { + if ((_permissionFlag & 0x40) == 0) + { + ulong titleId = ApplicationLaunchProperty.GetByPid(context).TitleId; + + if (titleId != 0) + { + _titleId = titleId; + + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. + _ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); + _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag; + } + } + + if (_titleId != 0) + { + // TODO: Service store some private fields in another static object. + + if ((_permissionFlag & 0x8040) == 0) + { + // TODO: Service store TitleId and FreeCommunicationEnabled in another static object. + // When it's done it signal an event in this static object. + Logger.Stub?.PrintStub(LogClass.ServicePctl); + } + } + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(1001)] + // CheckFreeCommunicationPermission() + public ResultCode CheckFreeCommunicationPermission(ServiceCtx context) + { + if (_parentalControlFlag == ParentalControlFlagValue.FreeCommunication && _restrictionEnabled) + { + // TODO: It seems to checks if an entry exists in the FreeCommunicationApplicationList using the TitleId. + // Then it returns FreeCommunicationDisabled if the entry doesn't exist. + + return ResultCode.FreeCommunicationDisabled; + } + + _freeCommunicationEnabled = true; + + Logger.Stub?.PrintStub(LogClass.ServicePctl); + + return ResultCode.Success; + } + + [CommandCmif(1017)] // 10.0.0+ + // EndFreeCommunication() + public ResultCode EndFreeCommunication(ServiceCtx context) + { + _freeCommunicationEnabled = false; + + return ResultCode.Success; + } + + [CommandCmif(1013)] // 4.0.0+ + // ConfirmStereoVisionPermission() + public ResultCode ConfirmStereoVisionPermission(ServiceCtx context) + { + return IsStereoVisionPermittedImpl(); + } + + [CommandCmif(1018)] + // IsFreeCommunicationAvailable() + public ResultCode IsFreeCommunicationAvailable(ServiceCtx context) + { + if (_parentalControlFlag == ParentalControlFlagValue.FreeCommunication && _restrictionEnabled) + { + // TODO: It seems to checks if an entry exists in the FreeCommunicationApplicationList using the TitleId. + // Then it returns FreeCommunicationDisabled if the entry doesn't exist. + + return ResultCode.FreeCommunicationDisabled; + } + + Logger.Stub?.PrintStub(LogClass.ServicePctl); + + return ResultCode.Success; + } + + [CommandCmif(1031)] + // IsRestrictionEnabled() -> b8 + public ResultCode IsRestrictionEnabled(ServiceCtx context) + { + if ((_permissionFlag & 0x140) == 0) + { + return ResultCode.PermissionDenied; + } + + context.ResponseData.Write(_restrictionEnabled); + + return ResultCode.Success; + } + + [CommandCmif(1061)] // 4.0.0+ + // ConfirmStereoVisionRestrictionConfigurable() + public ResultCode ConfirmStereoVisionRestrictionConfigurable(ServiceCtx context) + { + if ((_permissionFlag & 2) == 0) + { + return ResultCode.PermissionDenied; + } + + if (_stereoVisionRestrictionConfigurable) + { + return ResultCode.Success; + } + else + { + return ResultCode.StereoVisionRestrictionConfigurableDisabled; + } + } + + [CommandCmif(1062)] // 4.0.0+ + // GetStereoVisionRestriction() -> bool + public ResultCode GetStereoVisionRestriction(ServiceCtx context) + { + if ((_permissionFlag & 0x200) == 0) + { + return ResultCode.PermissionDenied; + } + + bool stereoVisionRestriction = false; + + if (_stereoVisionRestrictionConfigurable) + { + stereoVisionRestriction = _stereoVisionRestriction; + } + + context.ResponseData.Write(stereoVisionRestriction); + + return ResultCode.Success; + } + + [CommandCmif(1063)] // 4.0.0+ + // SetStereoVisionRestriction(bool) + public ResultCode SetStereoVisionRestriction(ServiceCtx context) + { + if ((_permissionFlag & 0x200) == 0) + { + return ResultCode.PermissionDenied; + } + + bool stereoVisionRestriction = context.RequestData.ReadBoolean(); + + if (!_featuresRestriction) + { + if (_stereoVisionRestrictionConfigurable) + { + _stereoVisionRestriction = stereoVisionRestriction; + + // TODO: It signals an internal event of service. We have to determine where this event is used. + } + } + + return ResultCode.Success; + } + + [CommandCmif(1064)] // 5.0.0+ + // ResetConfirmedStereoVisionPermission() + public ResultCode ResetConfirmedStereoVisionPermission(ServiceCtx context) + { + return ResultCode.Success; + } + + [CommandCmif(1065)] // 5.0.0+ + // IsStereoVisionPermitted() -> bool + public ResultCode IsStereoVisionPermitted(ServiceCtx context) + { + bool isStereoVisionPermitted = false; + + ResultCode resultCode = IsStereoVisionPermittedImpl(); + + if (resultCode == ResultCode.Success) + { + isStereoVisionPermitted = true; + } + + context.ResponseData.Write(isStereoVisionPermitted); + + return resultCode; + } + + private ResultCode IsStereoVisionPermittedImpl() + { + /* + // TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata. + // Since we don't support the pctl savedata for now, this can be implemented later. + + if (appExemption) + { + return ResultCode.Success; + } + */ + + if (_stereoVisionRestrictionConfigurable && _stereoVisionRestriction) + { + return ResultCode.StereoVisionDenied; + } + else + { + return ResultCode.Success; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs new file mode 100644 index 00000000..fcf06ee9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Pctl +{ + enum ResultCode + { + ModuleId = 142, + ErrorCodeShift = 9, + + Success = 0, + + FreeCommunicationDisabled = (101 << ErrorCodeShift) | ModuleId, + StereoVisionDenied = (104 << ErrorCodeShift) | ModuleId, + InvalidPid = (131 << ErrorCodeShift) | ModuleId, + PermissionDenied = (133 << ErrorCodeShift) | ModuleId, + StereoVisionRestrictionConfigurableDisabled = (181 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs new file mode 100644 index 00000000..7d0222d5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc")] + class IBoardPowerControlManager : IpcService + { + public IBoardPowerControlManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs new file mode 100644 index 00000000..e185c478 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs @@ -0,0 +1,32 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc:r")] // 1.0.0 - 8.1.0 + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + + [CommandCmif(0)] + // GetRtcTime() -> u64 + public ResultCode GetRtcTime(ServiceCtx context) + { + ResultCode result = GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + public static ResultCode GetExternalRtcValue(out ulong rtcValue) + { + // TODO: emulate MAX77620/MAX77812 RTC + rtcValue = (ulong)(DateTime.Now.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds; + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs new file mode 100644 index 00000000..b81e7fee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs @@ -0,0 +1,62 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Pcv.Types; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst.ClkrstManager +{ + class IClkrstSession : IpcService + { + private DeviceCode _deviceCode; + private uint _unknown; + private uint _clockRate; + + private DeviceCode[] allowedDeviceCodeTable = new DeviceCode[] + { + DeviceCode.Cpu, DeviceCode.Gpu, DeviceCode.Disp1, DeviceCode.Disp2, + DeviceCode.Tsec, DeviceCode.Mselect, DeviceCode.Sor1, DeviceCode.Host1x, + DeviceCode.Vic, DeviceCode.Nvenc, DeviceCode.Nvjpg, DeviceCode.Nvdec, + DeviceCode.Ape, DeviceCode.AudioDsp, DeviceCode.Emc, DeviceCode.Dsi, + DeviceCode.SysBus, DeviceCode.XusbSs, DeviceCode.XusbHost, DeviceCode.XusbDevice, + DeviceCode.Gpuaux, DeviceCode.Pcie, DeviceCode.Apbdma, DeviceCode.Sdmmc1, + DeviceCode.Sdmmc2, DeviceCode.Sdmmc4 + }; + + public IClkrstSession(DeviceCode deviceCode, uint unknown) + { + _deviceCode = deviceCode; + _unknown = unknown; + } + + [CommandCmif(7)] + // SetClockRate(u32 hz) + public ResultCode SetClockRate(ServiceCtx context) + { + if (!allowedDeviceCodeTable.Contains(_deviceCode)) + { + return ResultCode.InvalidArgument; + } + + _clockRate = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServicePcv, new { _clockRate }); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // GetClockRate() -> u32 hz + public ResultCode GetClockRate(ServiceCtx context) + { + if (!allowedDeviceCodeTable.Contains(_deviceCode)) + { + return ResultCode.InvalidArgument; + } + + context.ResponseData.Write(_clockRate); + + Logger.Stub?.PrintStub(LogClass.ServicePcv, new { _clockRate }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs new file mode 100644 index 00000000..6f1e5d25 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst:a")] // 8.0.0+ + class IArbitrationManager : IpcService + { + public IArbitrationManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs new file mode 100644 index 00000000..4ba2f094 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs @@ -0,0 +1,57 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Pcv.Clkrst.ClkrstManager; +using Ryujinx.HLE.HOS.Services.Pcv.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst")] // 8.0.0+ + [Service("clkrst:i")] // 8.0.0+ + class IClkrstManager : IpcService + { + private int _moduleStateTableEventHandle = 0; + + public IClkrstManager(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenSession(u32 device_code, u32 unk) -> object<nn::clkrst::IClkrstSession> + public ResultCode OpenSession(ServiceCtx context) + { + DeviceCode deviceCode = (DeviceCode)context.RequestData.ReadUInt32(); + uint unknown = context.RequestData.ReadUInt32(); + + // TODO: Service checks the deviceCode and the unk value. + + MakeObject(context, new IClkrstSession(deviceCode, unknown)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetModuleStateTableEvent() -> handle<copy> + public ResultCode GetModuleStateTableEvent(ServiceCtx context) + { + if (_moduleStateTableEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _moduleStateTableEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_moduleStateTableEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetModuleStateTableMaxCount() -> u32 max_count + public ResultCode GetModuleStateTableMaxCount(ServiceCtx context) + { + context.ResponseData.Write(26u); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs new file mode 100644 index 00000000..0e74dc3e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv +{ + [Service("pcv")] + class IPcvService : IpcService + { + public IPcvService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs new file mode 100644 index 00000000..2041e423 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv +{ + enum ResultCode + { + ModuleId = 30, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (5 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs new file mode 100644 index 00000000..f7834777 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rgltr +{ + [Service("rgltr")] // 8.0.0+ + class IRegulatorManager : IpcService + { + public IRegulatorManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs new file mode 100644 index 00000000..2b4a1239 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rtc +{ + [Service("rtc")] // 8.0.0+ + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs new file mode 100644 index 00000000..5380d82f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs @@ -0,0 +1,94 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Types +{ + enum DeviceCode + { + Cpu = 0x40000001, + Gpu = 0x40000002, + I2s1 = 0x40000003, + I2s2 = 0x40000004, + I2s3 = 0x40000005, + Pwm = 0x40000006, + I2c1 = 0x02000001, + I2c2 = 0x02000002, + I2c3 = 0x02000003, + I2c4 = 0x02000004, + I2c5 = 0x02000005, + I2c6 = 0x02000006, + Spi1 = 0x07000000, + Spi2 = 0x07000001, + Spi3 = 0x07000002, + Spi4 = 0x07000003, + Disp1 = 0x40000011, + Disp2 = 0x40000012, + Isp = 0x40000013, + Vi = 0x40000014, + Sdmmc1 = 0x40000015, + Sdmmc2 = 0x40000016, + Sdmmc3 = 0x40000017, + Sdmmc4 = 0x40000018, + Owr = 0x40000019, + Csite = 0x4000001A, + Tsec = 0x4000001B, + Mselect = 0x4000001C, + Hda2codec2x = 0x4000001D, + Actmon = 0x4000001E, + I2cSlow = 0x4000001F, + Sor1 = 0x40000020, + Sata = 0x40000021, + Hda = 0x40000022, + XusbCoreHostSrc = 0x40000023, + XusbFalconSrc = 0x40000024, + XusbFsSrc = 0x40000025, + XusbCoreDevSrc = 0x40000026, + XusbSsSrc = 0x40000027, + UartA = 0x03000001, + UartB = 0x35000405, + UartC = 0x3500040F, + UartD = 0x37000001, + Host1x = 0x4000002C, + Entropy = 0x4000002D, + SocTherm = 0x4000002E, + Vic = 0x4000002F, + Nvenc = 0x40000030, + Nvjpg = 0x40000031, + Nvdec = 0x40000032, + Qspi = 0x40000033, + ViI2c = 0x40000034, + Tsecb = 0x40000035, + Ape = 0x40000036, + AudioDsp = 0x40000037, + AudioUart = 0x40000038, + Emc = 0x40000039, + Plle = 0x4000003A, + PlleHwSeq = 0x4000003B, + Dsi = 0x4000003C, + Maud = 0x4000003D, + Dpaux1 = 0x4000003E, + MipiCal = 0x4000003F, + UartFstMipiCal = 0x40000040, + Osc = 0x40000041, + SysBus = 0x40000042, + SorSafe = 0x40000043, + XusbSs = 0x40000044, + XusbHost = 0x40000045, + XusbDevice = 0x40000046, + Extperiph1 = 0x40000047, + Ahub = 0x40000048, + Hda2hdmicodec = 0x40000049, + Gpuaux = 0x4000004A, + UsbD = 0x4000004B, + Usb2 = 0x4000004C, + Pcie = 0x4000004D, + Afi = 0x4000004E, + PciExClk = 0x4000004F, + PExUsbPhy = 0x40000050, + XUsbPadCtl = 0x40000051, + Apbdma = 0x40000052, + Usb2TrkClk = 0x40000053, + XUsbIoPll = 0x40000054, + XUsbIoPllHwSeq = 0x40000055, + Cec = 0x40000056, + Extperiph2 = 0x40000057, + OscClk = 0x40000080 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs new file mode 100644 index 00000000..45771db6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:bm")] + class IBootModeInterface : IpcService + { + public IBootModeInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs new file mode 100644 index 00000000..cce2967a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + + [CommandCmif(4)] + // GetProgramId() -> sf::Out<ncm::ProgramId> out_process_id + public ResultCode GetApplicationProcessId(ServiceCtx context) + { + // TODO: Not correct as it shouldn't be directly using kernel objects here + foreach (KProcess process in context.Device.System.KernelContext.Processes.Values) + { + if (process.IsApplication) + { + context.ResponseData.Write(process.Pid); + + return ResultCode.Success; + } + } + + return ResultCode.ProcessNotFound; + } + + [CommandCmif(65000)] + // AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status + public ResultCode GetProcessInfo(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + KProcess process = KernelStatic.GetProcessByPid(pid); + + if (context.Process.HandleTable.GenerateHandle(process, out int processHandle) != Result.Success) + { + throw new System.Exception("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(processHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs new file mode 100644 index 00000000..b3b5595f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:info")] + class IInformationInterface : IpcService + { + public IInformationInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetProgramId(os::ProcessId process_id) -> sf::Out<ncm::ProgramId> out + public ResultCode GetProgramId(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + // TODO: Not correct as it shouldn't be directly using kernel objects here + if (context.Device.System.KernelContext.Processes.TryGetValue(pid, out KProcess process)) + { + context.ResponseData.Write(process.TitleId); + + return ResultCode.Success; + } + + return ResultCode.ProcessNotFound; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs new file mode 100644 index 00000000..96202326 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:shell")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + + [CommandCmif(6)] + // GetApplicationPid() -> u64 + public ResultCode GetApplicationPid(ServiceCtx context) + { + // FIXME: This is wrong but needed to make hb loader works + // TODO: Change this when we will have a way to process via a PM like interface. + ulong pid = context.Process.Pid; + + context.ResponseData.Write(pid); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs new file mode 100644 index 00000000..92b5925e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + enum ResultCode + { + ModuleId = 15, + ErrorCodeShift = 9, + + Success = 0, + + ProcessNotFound = (1 << ErrorCodeShift) | ModuleId, + AlreadyStarted = (2 << ErrorCodeShift) | ModuleId, + NotTerminated = (3 << ErrorCodeShift) | ModuleId, + DebugHookInUse = (4 << ErrorCodeShift) | ModuleId, + ApplicationRunning = (5 << ErrorCodeShift) | ModuleId, + InvalidSize = (6 << ErrorCodeShift) | ModuleId, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs new file mode 100644 index 00000000..3810c282 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:c")] + class IPmControl : IpcService + { + public IPmControl(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs new file mode 100644 index 00000000..c8dfb32e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:m")] + class IPmService : IpcService + { + public IPmService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs b/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs new file mode 100644 index 00000000..ef48fa41 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Psc +{ + [Service("psc:l")] // 9.0.0+ + class IPmUnknown : IpcService + { + public IPmUnknown(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs new file mode 100644 index 00000000..e2fe2235 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fan +{ + [Service("fan")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs new file mode 100644 index 00000000..a93f5283 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm:dbg")] // 9.0.0+ + class IDebugger : IpcService + { + public IDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs new file mode 100644 index 00000000..0e3f965b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm")] // 9.0.0+ + [Service("fgm:0")] // 9.0.0+ + [Service("fgm:9")] // 9.0.0+ + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs new file mode 100644 index 00000000..0bec45fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Pcm +{ + [Service("pcm")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs new file mode 100644 index 00000000..4e3d3e8e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs @@ -0,0 +1,45 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + [Service("psm")] + class IPsmServer : IpcService + { + public IPsmServer(ServiceCtx context) { } + + [CommandCmif(0)] + // GetBatteryChargePercentage() -> u32 + public static ResultCode GetBatteryChargePercentage(ServiceCtx context) + { + int chargePercentage = 100; + + context.ResponseData.Write(chargePercentage); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargePercentage }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetChargerType() -> u32 + public static ResultCode GetChargerType(ServiceCtx context) + { + ChargerType chargerType = ChargerType.ChargerOrDock; + + context.ResponseData.Write((int)chargerType); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargerType }); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // OpenSession() -> IPsmSession + public ResultCode OpenSession(ServiceCtx context) + { + MakeObject(context, new IPsmSession(context.Device.System)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs new file mode 100644 index 00000000..5d11f227 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs @@ -0,0 +1,88 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + class IPsmSession : IpcService + { + private KEvent _stateChangeEvent; + private int _stateChangeEventHandle; + + public IPsmSession(Horizon system) + { + _stateChangeEvent = new KEvent(system.KernelContext); + _stateChangeEventHandle = -1; + } + + [CommandCmif(0)] + // BindStateChangeEvent() -> KObject + public ResultCode BindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == -1) + { + Result resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle); + + if (resultCode != Result.Success) + { + return (ResultCode)resultCode.ErrorCode; + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // UnbindStateChangeEvent() + public ResultCode UnbindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle != -1) + { + context.Process.HandleTable.CloseHandle(_stateChangeEventHandle); + _stateChangeEventHandle = -1; + } + + Logger.Stub?.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // SetChargerTypeChangeEventEnabled(u8) + public ResultCode SetChargerTypeChangeEventEnabled(ServiceCtx context) + { + bool chargerTypeChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargerTypeChangeEventEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetPowerSupplyChangeEventEnabled(u8) + public ResultCode SetPowerSupplyChangeEventEnabled(ServiceCtx context) + { + bool powerSupplyChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { powerSupplyChangeEventEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // SetBatteryVoltageStateChangeEventEnabled(u8) + public ResultCode SetBatteryVoltageStateChangeEventEnabled(ServiceCtx context) + { + bool batteryVoltageStateChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { batteryVoltageStateChangeEventEnabled }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs new file mode 100644 index 00000000..3e239711 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + enum ChargerType + { + None, + ChargerOrDock, + UsbC + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs new file mode 100644 index 00000000..1daa4f5e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Tc +{ + [Service("tc")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs new file mode 100644 index 00000000..6ddc0aef --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ptm.Ts.Types; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Ts +{ + [Service("ts")] + class IMeasurementServer : IpcService + { + private const uint DefaultTemperature = 42u; + + public IMeasurementServer(ServiceCtx context) { } + + [CommandCmif(1)] + // GetTemperature(Location location) -> u32 + public ResultCode GetTemperature(ServiceCtx context) + { + Location location = (Location)context.RequestData.ReadByte(); + + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + context.ResponseData.Write(DefaultTemperature); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetTemperatureMilliC(Location location) -> u32 + public ResultCode GetTemperatureMilliC(ServiceCtx context) + { + Location location = (Location)context.RequestData.ReadByte(); + + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + context.ResponseData.Write(DefaultTemperature * 1000); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs new file mode 100644 index 00000000..e72491d5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Ts.Types +{ + enum Location : byte + { + Internal, + External + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs new file mode 100644 index 00000000..966adcff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -0,0 +1,602 @@ +using LibHac.Tools.FsSystem; +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [Service("ldr:ro")] + [Service("ro:1")] // 7.0.0+ + class IRoInterface : DisposableIpcService + { + private const int MaxNrr = 0x40; + private const int MaxNro = 0x40; + private const int MaxMapRetries = 0x200; + private const int GuardPagesSize = 0x4000; + + private const uint NrrMagic = 0x3052524E; + private const uint NroMagic = 0x304F524E; + + private List<NrrInfo> _nrrInfos; + private List<NroInfo> _nroInfos; + + private KProcess _owner; + private IVirtualMemoryManager _ownerMm; + + public IRoInterface(ServiceCtx context) + { + _nrrInfos = new List<NrrInfo>(MaxNrr); + _nroInfos = new List<NroInfo>(MaxNro); + _owner = null; + _ownerMm = null; + } + + private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize) + { + nrrInfo = null; + + if (nrrSize == 0 || nrrAddress + nrrSize <= nrrAddress || (nrrSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if ((nrrAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + NrrHeader header = _owner.CpuMemory.Read<NrrHeader>(nrrAddress); + + if (header.Magic != NrrMagic) + { + return ResultCode.InvalidNrr; + } + else if (header.Size != nrrSize) + { + return ResultCode.InvalidSize; + } + + List<byte[]> hashes = new List<byte[]>(); + + for (int i = 0; i < header.HashesCount; i++) + { + byte[] hash = new byte[0x20]; + + _owner.CpuMemory.Read(nrrAddress + header.HashesOffset + (uint)(i * 0x20), hash); + + hashes.Add(hash); + } + + nrrInfo = new NrrInfo(nrrAddress, header, hashes); + + return ResultCode.Success; + } + + public bool IsNroHashPresent(byte[] nroHash) + { + foreach (NrrInfo info in _nrrInfos) + { + foreach (byte[] hash in info.Hashes) + { + if (hash.SequenceEqual(nroHash)) + { + return true; + } + } + } + + return false; + } + + public bool IsNroLoaded(byte[] nroHash) + { + foreach (NroInfo info in _nroInfos) + { + if (info.Hash.SequenceEqual(nroHash)) + { + return true; + } + } + + return false; + } + + public ResultCode ParseNro(out NroInfo res, ServiceCtx context, ulong nroAddress, ulong nroSize, ulong bssAddress, ulong bssSize) + { + res = null; + + if (_nroInfos.Count >= MaxNro) + { + return ResultCode.TooManyNro; + } + else if (nroSize == 0 || nroAddress + nroSize <= nroAddress || (nroSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if (bssSize != 0 && bssAddress + bssSize <= bssAddress) + { + return ResultCode.InvalidSize; + } + else if ((nroAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + uint magic = _owner.CpuMemory.Read<uint>(nroAddress + 0x10); + uint nroFileSize = _owner.CpuMemory.Read<uint>(nroAddress + 0x18); + + if (magic != NroMagic || nroSize != nroFileSize) + { + return ResultCode.InvalidNro; + } + + byte[] nroData = new byte[nroSize]; + + _owner.CpuMemory.Read(nroAddress, nroData); + + MemoryStream stream = new MemoryStream(nroData); + + byte[] nroHash = SHA256.HashData(stream); + + if (!IsNroHashPresent(nroHash)) + { + return ResultCode.NotRegistered; + } + + if (IsNroLoaded(nroHash)) + { + return ResultCode.AlreadyLoaded; + } + + stream.Position = 0; + + NroExecutable nro = new NroExecutable(stream.AsStorage(), nroAddress, bssAddress); + + // Check if everything is page align. + if ((nro.Text.Length & 0xFFF) != 0 || (nro.Ro.Length & 0xFFF) != 0 || + (nro.Data.Length & 0xFFF) != 0 || (nro.BssSize & 0xFFF) != 0) + { + return ResultCode.InvalidNro; + } + + // Check if everything is contiguous. + if (nro.RoOffset != nro.TextOffset + nro.Text.Length || + nro.DataOffset != nro.RoOffset + nro.Ro.Length || + nroFileSize != nro.DataOffset + nro.Data.Length) + { + return ResultCode.InvalidNro; + } + + // Check the bss size match. + if ((ulong)nro.BssSize != bssSize) + { + return ResultCode.InvalidNro; + } + + uint totalSize = (uint)nro.Text.Length + (uint)nro.Ro.Length + (uint)nro.Data.Length + nro.BssSize; + + // Apply patches + context.Device.FileSystem.ModLoader.ApplyNroPatches(nro); + + res = new NroInfo( + nro, + nroHash, + nroAddress, + nroSize, + bssAddress, + bssSize, + (ulong)totalSize); + + return ResultCode.Success; + } + + private ResultCode MapNro(KProcess process, NroInfo info, out ulong nroMappedAddress) + { + KPageTableBase memMgr = process.MemoryManager; + + int retryCount = 0; + + nroMappedAddress = 0; + + while (retryCount++ < MaxMapRetries) + { + ResultCode result = MapCodeMemoryInProcess(process, info.NroAddress, info.NroSize, out nroMappedAddress); + + if (result != ResultCode.Success) + { + return result; + } + + if (info.BssSize > 0) + { + Result bssMappingResult = memMgr.MapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + + if (bssMappingResult == KernelResult.InvalidMemState) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + continue; + } + else if (bssMappingResult != Result.Success) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + return (ResultCode)bssMappingResult.ErrorCode; + } + } + + if (CanAddGuardRegionsInProcess(process, nroMappedAddress, info.TotalSize)) + { + return ResultCode.Success; + } + } + + return ResultCode.InsufficientAddressSpace; + } + + private bool CanAddGuardRegionsInProcess(KProcess process, ulong baseAddress, ulong size) + { + KPageTableBase memMgr = process.MemoryManager; + + KMemoryInfo memInfo = memMgr.QueryMemory(baseAddress - 1); + + if (memInfo.State == MemoryState.Unmapped && baseAddress - GuardPagesSize >= memInfo.Address) + { + memInfo = memMgr.QueryMemory(baseAddress + size); + + if (memInfo.State == MemoryState.Unmapped) + { + return baseAddress + size + GuardPagesSize <= memInfo.Address + memInfo.Size; + } + } + return false; + } + + private ResultCode MapCodeMemoryInProcess(KProcess process, ulong baseAddress, ulong size, out ulong targetAddress) + { + KPageTableBase memMgr = process.MemoryManager; + + targetAddress = 0; + + int retryCount; + + ulong addressSpacePageLimit = (memMgr.GetAddrSpaceSize() - size) >> 12; + + for (retryCount = 0; retryCount < MaxMapRetries; retryCount++) + { + while (true) + { + ulong randomOffset = (ulong)(uint)Random.Shared.Next(0, (int)addressSpacePageLimit) << 12; + + targetAddress = memMgr.GetAddrSpaceBaseAddr() + randomOffset; + + if (memMgr.InsideAddrSpace(targetAddress, size) && !memMgr.InsideHeapRegion(targetAddress, size) && !memMgr.InsideAliasRegion(targetAddress, size)) + { + break; + } + } + + Result result = memMgr.MapProcessCodeMemory(targetAddress, baseAddress, size); + + if (result == KernelResult.InvalidMemState) + { + continue; + } + else if (result != Result.Success) + { + return (ResultCode)result.ErrorCode; + } + + if (!CanAddGuardRegionsInProcess(process, targetAddress, size)) + { + continue; + } + + return ResultCode.Success; + } + + if (retryCount == MaxMapRetries) + { + return ResultCode.InsufficientAddressSpace; + } + + return ResultCode.Success; + } + + private Result SetNroMemoryPermissions(KProcess process, IExecutable relocatableObject, ulong baseAddress) + { + ulong textStart = baseAddress + relocatableObject.TextOffset; + ulong roStart = baseAddress + relocatableObject.RoOffset; + ulong dataStart = baseAddress + relocatableObject.DataOffset; + + ulong bssStart = dataStart + (ulong)relocatableObject.Data.Length; + + ulong bssEnd = BitUtils.AlignUp<ulong>(bssStart + relocatableObject.BssSize, KPageTableBase.PageSize); + + process.CpuMemory.Write(textStart, relocatableObject.Text); + process.CpuMemory.Write(roStart, relocatableObject.Ro); + process.CpuMemory.Write(dataStart, relocatableObject.Data); + + MemoryHelper.FillWithZeros(process.CpuMemory, bssStart, (int)(bssEnd - bssStart)); + + Result result; + + result = process.MemoryManager.SetProcessMemoryPermission(textStart, roStart - textStart, KMemoryPermission.ReadAndExecute); + + if (result != Result.Success) + { + return result; + } + + result = process.MemoryManager.SetProcessMemoryPermission(roStart, dataStart - roStart, KMemoryPermission.Read); + + if (result != Result.Success) + { + return result; + } + + return process.MemoryManager.SetProcessMemoryPermission(dataStart, bssEnd - dataStart, KMemoryPermission.ReadAndWrite); + } + + private ResultCode RemoveNrrInfo(ulong nrrAddress) + { + foreach (NrrInfo info in _nrrInfos) + { + if (info.NrrAddress == nrrAddress) + { + _nrrInfos.Remove(info); + + return ResultCode.Success; + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode RemoveNroInfo(ulong nroMappedAddress) + { + foreach (NroInfo info in _nroInfos) + { + if (info.NroMappedAddress == nroMappedAddress) + { + _nroInfos.Remove(info); + + return UnmapNroFromInfo(info); + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode UnmapNroFromInfo(NroInfo info) + { + ulong textSize = (ulong)info.Executable.Text.Length; + ulong roSize = (ulong)info.Executable.Ro.Length; + ulong dataSize = (ulong)info.Executable.Data.Length; + ulong bssSize = (ulong)info.Executable.BssSize; + + Result result = Result.Success; + + if (info.Executable.BssSize != 0) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize + dataSize, + info.Executable.BssAddress, + bssSize); + } + + if (result == Result.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize, + info.Executable.SourceAddress + textSize + roSize, + dataSize); + + if (result == Result.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress, + info.Executable.SourceAddress, + textSize + roSize); + } + } + + return (ResultCode)result.ErrorCode; + } + + private ResultCode IsInitialized(ulong pid) + { + if (_owner != null && _owner.Pid == pid) + { + return ResultCode.Success; + } + + return ResultCode.InvalidProcess; + } + + [CommandCmif(0)] + // LoadNro(u64, u64, u64, u64, u64, pid) -> u64 + public ResultCode LoadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroHeapAddress = context.RequestData.ReadUInt64(); + ulong nroSize = context.RequestData.ReadUInt64(); + ulong bssHeapAddress = context.RequestData.ReadUInt64(); + ulong bssSize = context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = 0; + + if (result == ResultCode.Success) + { + NroInfo info; + + result = ParseNro(out info, context, nroHeapAddress, nroSize, bssHeapAddress, bssSize); + + if (result == ResultCode.Success) + { + result = MapNro(_owner, info, out nroMappedAddress); + + if (result == ResultCode.Success) + { + result = (ResultCode)SetNroMemoryPermissions(_owner, info.Executable, nroMappedAddress).ErrorCode; + + if (result == ResultCode.Success) + { + info.NroMappedAddress = nroMappedAddress; + + _nroInfos.Add(info); + } + } + } + } + + context.ResponseData.Write(nroMappedAddress); + + return result; + } + + [CommandCmif(1)] + // UnloadNro(u64, u64, pid) + public ResultCode UnloadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + if ((nroMappedAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNroInfo(nroMappedAddress); + } + + return result; + } + + [CommandCmif(2)] + // LoadNrr(u64, u64, u64, pid) + public ResultCode LoadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + ulong nrrAddress = context.RequestData.ReadUInt64(); + ulong nrrSize = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + NrrInfo info; + result = ParseNrr(out info, context, nrrAddress, nrrSize); + + if (result == ResultCode.Success) + { + if (_nrrInfos.Count >= MaxNrr) + { + result = ResultCode.NotLoaded; + } + else + { + _nrrInfos.Add(info); + } + } + } + + return result; + } + + [CommandCmif(3)] + // UnloadNrr(u64, u64, pid) + public ResultCode UnloadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + ulong nrrHeapAddress = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + if ((nrrHeapAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNrrInfo(nrrHeapAddress); + } + + return result; + } + + [CommandCmif(4)] + // Initialize(u64, pid, KObject) + public ResultCode Initialize(ServiceCtx context) + { + if (_owner != null) + { + return ResultCode.InvalidSession; + } + + int processHandle = context.Request.HandleDesc.ToCopy[0]; + _owner = context.Process.HandleTable.GetKProcess(processHandle); + _ownerMm = _owner?.CpuMemory; + context.Device.System.KernelContext.Syscall.CloseHandle(processHandle); + + if (_ownerMm is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + return ResultCode.Success; + } + + [CommandCmif(10)] + // LoadNrr2(u64, u64, u64, pid) + public ResultCode LoadNrr2(ServiceCtx context) + { + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return LoadNrr(context); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + foreach (NroInfo info in _nroInfos) + { + UnmapNroFromInfo(info); + } + + _nroInfos.Clear(); + + if (_ownerMm is IRefCounted rc) + { + rc.DecrementReferenceCount(); + } + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs new file mode 100644 index 00000000..92bb5502 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Ro +{ + enum ResultCode + { + ModuleId = 22, + ErrorCodeShift = 9, + + Success = 0, + + InsufficientAddressSpace = (2 << ErrorCodeShift) | ModuleId, + AlreadyLoaded = (3 << ErrorCodeShift) | ModuleId, + InvalidNro = (4 << ErrorCodeShift) | ModuleId, + InvalidNrr = (6 << ErrorCodeShift) | ModuleId, + TooManyNro = (7 << ErrorCodeShift) | ModuleId, + TooManyNrr = (8 << ErrorCodeShift) | ModuleId, + NotAuthorized = (9 << ErrorCodeShift) | ModuleId, + + InvalidNrrType = (10 << ErrorCodeShift) | ModuleId, + + InvalidAddress = (1025 << ErrorCodeShift) | ModuleId, + InvalidSize = (1026 << ErrorCodeShift) | ModuleId, + NotLoaded = (1028 << ErrorCodeShift) | ModuleId, + NotRegistered = (1029 << ErrorCodeShift) | ModuleId, + InvalidSession = (1030 << ErrorCodeShift) | ModuleId, + InvalidProcess = (1031 << ErrorCodeShift) | ModuleId, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs new file mode 100644 index 00000000..8c56adb9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [StructLayout(LayoutKind.Sequential, Size = 0x220)] + struct NRRCertification + { + public ulong ApplicationIdMask; + public ulong ApplicationIdPattern; + private Array16<byte> _reserved; + public ByteArray256 Modulus; + public ByteArray256 Signature; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs new file mode 100644 index 00000000..45daf1bd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.Loaders.Executables; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NroInfo + { + public NroExecutable Executable { get; private set; } + + public byte[] Hash { get; private set; } + public ulong NroAddress { get; private set; } + public ulong NroSize { get; private set; } + public ulong BssAddress { get; private set; } + public ulong BssSize { get; private set; } + public ulong TotalSize { get; private set; } + public ulong NroMappedAddress { get; set; } + + public NroInfo( + NroExecutable executable, + byte[] hash, + ulong nroAddress, + ulong nroSize, + ulong bssAddress, + ulong bssSize, + ulong totalSize) + { + Executable = executable; + Hash = hash; + NroAddress = nroAddress; + NroSize = nroSize; + BssAddress = bssAddress; + BssSize = bssSize; + TotalSize = totalSize; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs new file mode 100644 index 00000000..dbbcb151 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs @@ -0,0 +1,22 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [StructLayout(LayoutKind.Sequential, Size = 0x350)] + struct NrrHeader + { + public uint Magic; + public uint KeyGeneration; // 9.0.0+ + private Array8<byte> _reserved; + public NRRCertification Certification; + public ByteArray256 Signature; + public ulong TitleId; + public uint Size; + public byte Kind; // 7.0.0+ + private Array3<byte> _reserved2; + public uint HashesOffset; + public uint HashesCount; + private Array8<byte> _reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs new file mode 100644 index 00000000..45c34f1c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NrrInfo + { + public NrrHeader Header { get; private set; } + public List<byte[]> Hashes { get; private set; } + public ulong NrrAddress { get; private set; } + + public NrrInfo(ulong nrrAddress, NrrHeader header, List<byte[]> hashes) + { + NrrAddress = nrrAddress; + Header = header; + Hashes = hashes; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs new file mode 100644 index 00000000..d65c8bba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("avm")] // 6.0.0+ + class IAvmService : IpcService + { + public IAvmService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs new file mode 100644 index 00000000..5247a238 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:ntfy")] + class INotifyService : IpcService + { + public INotifyService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs new file mode 100644 index 00000000..1f66ff9d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:qry")] + class IQueryService : IpcService + { + public IQueryService(ServiceCtx context) { } + + [CommandCmif(13)] // 5.0.0+ + // QueryApplicationPlayStatisticsForSystem(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [CommandCmif(16)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUserAccountIdForSystem(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUserAccountIdForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs new file mode 100644 index 00000000..52a07d46 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService +{ + static class QueryPlayStatisticsManager + { + private static Dictionary<UserId, ApplicationPlayStatistics> applicationPlayStatistics = new Dictionary<UserId, ApplicationPlayStatistics>(); + + internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + UserId userId = byUserId ? context.RequestData.ReadStruct<UserId>() : new UserId(); + + if (byUserId) + { + if (!context.Device.System.AccountManager.TryGetUser(userId, out _)) + { + return ResultCode.UserNotFound; + } + } + + PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability; + + List<ulong> titleIds = new List<ulong>(); + + for (ulong i = 0; i < inputSize / sizeof(ulong); i++) + { + titleIds.Add(context.Memory.Read<ulong>(inputPosition)); + } + + if (queryCapability == PlayLogQueryCapability.WhiteList) + { + // Check if input title ids are in the whitelist. + foreach (ulong titleId in titleIds) + { + if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) + { + return (ResultCode)Am.ResultCode.ObjectInvalid; + } + } + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + // Return ResultCode.ServiceUnavailable if data is locked by another process. + var filteredApplicationPlayStatistics = applicationPlayStatistics.AsEnumerable(); + + if (queryCapability == PlayLogQueryCapability.None) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Value.TitleId == context.Process.TitleId); + } + else // PlayLogQueryCapability.All + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => titleIds.Contains(kv.Value.TitleId)); + } + + if (byUserId) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Key == userId); + } + + for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++) + { + MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Unsafe.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value); + } + + context.ResponseData.Write(filteredApplicationPlayStatistics.Count()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs new file mode 100644 index 00000000..c28d757e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct ApplicationPlayStatistics + { + public ulong TitleId; + public long TotalPlayTime; // In nanoseconds. + public long TotalLaunchCount; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs new file mode 100644 index 00000000..9e4b85de --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + enum PlayLogQueryCapability + { + None, + WhiteList, + All + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs new file mode 100644 index 00000000..c337051b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + enum ResultCode + { + ModuleId = 178, + ErrorCodeShift = 9, + + Success = 0, + + InvalidUserID = (100 << ErrorCodeShift) | ModuleId, + UserNotFound = (101 << ErrorCodeShift) | ModuleId, + ServiceUnavailable = (150 << ErrorCodeShift) | ModuleId, + FileStorageFailure = (200 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs new file mode 100644 index 00000000..9e2f7a4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs @@ -0,0 +1,140 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Sdb.Pl.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl +{ + [Service("pl:u")] + [Service("pl:s")] // 9.0.0+ + class ISharedFontManager : IpcService + { + private int _fontSharedMemHandle; + + public ISharedFontManager(ServiceCtx context) { } + + [CommandCmif(0)] + // RequestLoad(u32) + public ResultCode RequestLoad(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + // We don't need to do anything here because we do lazy initialization + // on SharedFontManager (the font is loaded when necessary). + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetLoadState(u32) -> u32 + public ResultCode GetLoadState(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + // 1 (true) indicates that the font is already loaded. + // All fonts are already loaded. + context.ResponseData.Write(1); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetFontSize(u32) -> u32 + public ResultCode GetFontSize(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.SharedFontManager.GetFontSize(fontType)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetSharedMemoryAddressOffset(u32) -> u32 + public ResultCode GetSharedMemoryAddressOffset(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.SharedFontManager.GetSharedMemoryAddressOffset(fontType)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSharedMemoryNativeHandle() -> handle<copy> + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + context.Device.System.SharedFontManager.EnsureInitialized(context.Device.System.ContentManager); + + if (_fontSharedMemHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.FontSharedMem, out _fontSharedMemHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_fontSharedMemHandle); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetSharedFontInOrderOfPriority(bytes<8, 1>) -> (u8, u32, buffer<unknown, 6>, buffer<unknown, 6>, buffer<unknown, 6>) + public ResultCode GetSharedFontInOrderOfPriority(ServiceCtx context) + { + long languageCode = context.RequestData.ReadInt64(); + int loadedCount = 0; + + for (SharedFontType type = 0; type < SharedFontType.Count; type++) + { + uint offset = (uint)type * 4; + + if (!AddFontToOrderOfPriorityList(context, type, offset)) + { + break; + } + + loadedCount++; + } + + context.ResponseData.Write(loadedCount); + context.ResponseData.Write((int)SharedFontType.Count); + + return ResultCode.Success; + } + + [CommandCmif(6)] // 4.0.0+ + // GetSharedFontInOrderOfPriorityForSystem(bytes<8, 1>) -> (u8, u32, buffer<unknown, 6>, buffer<unknown, 6>, buffer<unknown, 6>) + public ResultCode GetSharedFontInOrderOfPriorityForSystem(ServiceCtx context) + { + // TODO: Check the differencies with GetSharedFontInOrderOfPriority. + + return GetSharedFontInOrderOfPriority(context); + } + + private bool AddFontToOrderOfPriorityList(ServiceCtx context, SharedFontType fontType, uint offset) + { + ulong typesPosition = context.Request.ReceiveBuff[0].Position; + ulong typesSize = context.Request.ReceiveBuff[0].Size; + + ulong offsetsPosition = context.Request.ReceiveBuff[1].Position; + ulong offsetsSize = context.Request.ReceiveBuff[1].Size; + + ulong fontSizeBufferPosition = context.Request.ReceiveBuff[2].Position; + ulong fontSizeBufferSize = context.Request.ReceiveBuff[2].Size; + + if (offset + 4 > (uint)typesSize || + offset + 4 > (uint)offsetsSize || + offset + 4 > (uint)fontSizeBufferSize) + { + return false; + } + + context.Memory.Write(typesPosition + offset, (int)fontType); + context.Memory.Write(offsetsPosition + offset, context.Device.System.SharedFontManager.GetSharedMemoryAddressOffset(fontType)); + context.Memory.Write(fontSizeBufferPosition + offset, context.Device.System.SharedFontManager.GetFontSize(fontType)); + + return true; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs new file mode 100644 index 00000000..fef82cbc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs @@ -0,0 +1,183 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Sdb.Pl.Types; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl +{ + class SharedFontManager + { + private static readonly uint FontKey = 0x06186249; + private static readonly uint BFTTFMagic = 0x18029a7f; + + private readonly Switch _device; + private readonly SharedMemoryStorage _storage; + + private struct FontInfo + { + public int Offset; + public int Size; + + public FontInfo(int offset, int size) + { + Offset = offset; + Size = size; + } + } + + private Dictionary<SharedFontType, FontInfo> _fontData; + + public SharedFontManager(Switch device, SharedMemoryStorage storage) + { + _device = device; + _storage = storage; + } + + public void Initialize() + { + _fontData?.Clear(); + _fontData = null; + + } + + public void EnsureInitialized(ContentManager contentManager) + { + if (_fontData == null) + { + _storage.ZeroFill(); + + uint fontOffset = 0; + + FontInfo CreateFont(string name) + { + if (contentManager.TryGetFontTitle(name, out ulong fontTitle) && contentManager.TryGetFontFilename(name, out string fontFilename)) + { + string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.BuiltInSystem, NcaContentType.Data); + string fontPath = _device.FileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(fontPath)) + { + byte[] data; + + using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open)) + { + Nca nca = new Nca(_device.System.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + using var fontFile = new UniqueRef<IFile>(); + + romfs.OpenFile(ref fontFile.Ref, ("/" + fontFilename).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + data = DecryptFont(fontFile.Get.AsStream()); + } + + FontInfo info = new FontInfo((int)fontOffset, data.Length); + + WriteMagicAndSize(fontOffset, data.Length); + + fontOffset += 8; + + uint start = fontOffset; + + for (; fontOffset - start < data.Length; fontOffset++) + { + _storage.GetRef<byte>(fontOffset) = data[fontOffset - start]; + } + + return info; + } + else + { + if (!contentManager.TryGetSystemTitlesName(fontTitle, out string titleName)) + { + titleName = "Unknown"; + } + + throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + } + } + else + { + throw new ArgumentException($"Unknown font \"{name}\"!"); + } + } + + _fontData = new Dictionary<SharedFontType, FontInfo> + { + { SharedFontType.JapanUsEurope, CreateFont("FontStandard") }, + { SharedFontType.SimplifiedChinese, CreateFont("FontChineseSimplified") }, + { SharedFontType.SimplifiedChineseEx, CreateFont("FontExtendedChineseSimplified") }, + { SharedFontType.TraditionalChinese, CreateFont("FontChineseTraditional") }, + { SharedFontType.Korean, CreateFont("FontKorean") }, + { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") } + }; + + if (fontOffset > Horizon.FontSize) + { + throw new InvalidSystemResourceException("The sum of all fonts size exceed the shared memory size. " + + $"Please make sure that the fonts don't exceed {Horizon.FontSize} bytes in total. (actual size: {fontOffset} bytes)."); + } + } + } + + private void WriteMagicAndSize(ulong offset, int size) + { + const int key = 0x49621806; + + int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ key); + + _storage.GetRef<int>(offset + 0) = (int)BFTTFMagic; + _storage.GetRef<int>(offset + 4) = encryptedSize; + } + + public int GetFontSize(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager); + + return _fontData[fontType].Size; + } + + public int GetSharedMemoryAddressOffset(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager); + + return _fontData[fontType].Offset + 8; + } + + private static byte[] DecryptFont(Stream bfttfStream) + { + static uint KXor(uint data) => data ^ FontKey; + + using (BinaryReader reader = new BinaryReader(bfttfStream)) + using (MemoryStream ttfStream = MemoryStreamManager.Shared.GetStream()) + using (BinaryWriter output = new BinaryWriter(ttfStream)) + { + if (KXor(reader.ReadUInt32()) != BFTTFMagic) + { + throw new InvalidDataException("Error: Input file is not in BFTTF format!"); + } + + bfttfStream.Position += 4; + + for (int i = 0; i < (bfttfStream.Length - 8) / 4; i++) + { + output.Write(KXor(reader.ReadUInt32())); + } + + return ttfStream.ToArray(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs new file mode 100644 index 00000000..90ee4f03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl.Types +{ + public enum SharedFontType + { + JapanUsEurope = 0, + SimplifiedChinese = 1, + SimplifiedChineseEx = 2, + TraditionalChinese = 3, + Korean = 4, + NintendoEx = 5, + Count + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs new file mode 100644 index 00000000..b994679a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -0,0 +1,423 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services +{ + class ServerBase : IDisposable + { + // Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000). + // Having a size that is too low will cause failures as data copy will fail if the receiving buffer is + // not large enough. + private const int PointerBufferSize = 0x8000; + + private readonly static uint[] DefaultCapabilities = new uint[] + { + 0x030363F7, + 0x1FFFFFCF, + 0x207FFFEF, + 0x47E0060F, + 0x0048BFFF, + 0x01007FFF + }; + + private readonly object _handleLock = new(); + + private readonly KernelContext _context; + private KProcess _selfProcess; + + private readonly List<int> _sessionHandles = new List<int>(); + private readonly List<int> _portHandles = new List<int>(); + private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>(); + private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>(); + + private readonly MemoryStream _requestDataStream; + private readonly BinaryReader _requestDataReader; + + private readonly MemoryStream _responseDataStream; + private readonly BinaryWriter _responseDataWriter; + + public ManualResetEvent InitDone { get; } + public string Name { get; } + public Func<IpcService> SmObjectFactory { get; } + + public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) + { + _context = context; + + _requestDataStream = MemoryStreamManager.Shared.GetStream(); + _requestDataReader = new BinaryReader(_requestDataStream); + + _responseDataStream = MemoryStreamManager.Shared.GetStream(); + _responseDataWriter = new BinaryWriter(_responseDataStream); + + InitDone = new ManualResetEvent(false); + Name = name; + SmObjectFactory = smObjectFactory; + + const ProcessCreationFlags flags = + ProcessCreationFlags.EnableAslr | + ProcessCreationFlags.AddressSpace64Bit | + ProcessCreationFlags.Is64Bit | + ProcessCreationFlags.PoolPartitionSystem; + + ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); + + KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main); + } + + private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) + { + lock (_handleLock) + { + _portHandles.Add(serverPortHandle); + } + _ports.Add(serverPortHandle, objectFactory); + } + + public void AddSessionObj(KServerSession serverSession, IpcService obj) + { + // Ensure that the sever loop is running. + InitDone.WaitOne(); + + _selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle); + AddSessionObj(serverSessionHandle, obj); + } + + public void AddSessionObj(int serverSessionHandle, IpcService obj) + { + lock (_handleLock) + { + _sessionHandles.Add(serverSessionHandle); + } + _sessions.Add(serverSessionHandle, obj); + } + + private void Main() + { + ServerLoop(); + } + + private void ServerLoop() + { + _selfProcess = KernelStatic.GetCurrentProcess(); + + if (SmObjectFactory != null) + { + _context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50); + + AddPort(serverPortHandle, SmObjectFactory); + } + + InitDone.Set(); + + KThread thread = KernelStatic.GetCurrentThread(); + ulong messagePtr = thread.TlsAddress; + _context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000); + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + + int replyTargetHandle = 0; + + while (true) + { + int handleCount; + int portHandleCount; + int[] handles; + + lock (_handleLock) + { + portHandleCount = _portHandles.Count; + handleCount = portHandleCount + _sessionHandles.Count; + + handles = ArrayPool<int>.Shared.Rent(handleCount); + + _portHandles.CopyTo(handles, 0); + _sessionHandles.CopyTo(handles, portHandleCount); + } + + // We still need a timeout here to allow the service to pick up and listen new sessions... + var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L); + + thread.HandlePostSyscall(); + + if (!thread.Context.Running) + { + break; + } + + replyTargetHandle = 0; + + if (rc == Result.Success && signaledIndex >= portHandleCount) + { + // We got a IPC request, process it, pass to the appropriate service if needed. + int signaledHandle = handles[signaledIndex]; + + if (Process(signaledHandle, heapAddr)) + { + replyTargetHandle = signaledHandle; + } + } + else + { + if (rc == Result.Success) + { + // We got a new connection, accept the session to allow servicing future requests. + if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success) + { + IpcService obj = _ports[handles[signaledIndex]].Invoke(); + + AddSessionObj(serverSessionHandle, obj); + } + } + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + } + + ArrayPool<int>.Shared.Return(handles); + } + + Dispose(); + } + + private bool Process(int serverSessionHandle, ulong recvListAddr) + { + KProcess process = KernelStatic.GetCurrentProcess(); + KThread thread = KernelStatic.GetCurrentThread(); + ulong messagePtr = thread.TlsAddress; + + IpcMessage request = ReadRequest(process, messagePtr); + + IpcMessage response = new IpcMessage(); + + ulong tempAddr = recvListAddr; + int sizesOffset = request.RawData.Length - ((request.RecvListBuff.Count * 2 + 3) & ~3); + + bool noReceive = true; + + for (int i = 0; i < request.ReceiveBuff.Count; i++) + { + noReceive &= (request.ReceiveBuff[i].Position == 0); + } + + if (noReceive) + { + response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count); + + for (int i = 0; i < request.RecvListBuff.Count; i++) + { + ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2)); + + response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size)); + + request.RecvListBuff[i] = new IpcRecvListBuffDesc(tempAddr, size); + + tempAddr += size; + } + } + + bool shouldReply = true; + bool isTipcCommunication = false; + + _requestDataStream.SetLength(0); + _requestDataStream.Write(request.RawData); + _requestDataStream.Position = 0; + + if (request.Type == IpcMessageType.CmifRequest || + request.Type == IpcMessageType.CmifRequestWithContext) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); + + _sessions[serverSessionHandle].CallCmifMethod(context); + + response.RawData = _responseDataStream.ToArray(); + } + else if (request.Type == IpcMessageType.CmifControl || + request.Type == IpcMessageType.CmifControlWithContext) + { + uint magic = (uint)_requestDataReader.ReadUInt64(); + uint cmdId = (uint)_requestDataReader.ReadUInt64(); + + switch (cmdId) + { + case 0: + FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain()); + break; + + case 3: + FillHipcResponse(response, 0, PointerBufferSize); + break; + + // TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: + case 4: + int unknown = _requestDataReader.ReadInt32(); + + _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); + + AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]); + + response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); + + FillHipcResponse(response, 0); + + break; + + default: throw new NotImplementedException(cmdId.ToString()); + } + } + else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession) + { + _context.Syscall.CloseHandle(serverSessionHandle); + lock (_handleLock) + { + _sessionHandles.Remove(serverSessionHandle); + } + IpcService service = _sessions[serverSessionHandle]; + (service as IDisposable)?.Dispose(); + _sessions.Remove(serverSessionHandle); + shouldReply = false; + } + // If the type is past 0xF, we are using TIPC + else if (request.Type > IpcMessageType.TipcCloseSession) + { + isTipcCommunication = true; + + // Response type is always the same as request on TIPC. + response.Type = request.Type; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); + + _sessions[serverSessionHandle].CallTipcMethod(context); + + response.RawData = _responseDataStream.ToArray(); + + using var responseStream = response.GetStreamTipc(); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + else + { + throw new NotImplementedException(request.Type.ToString()); + } + + if (!isTipcCommunication) + { + using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + + return shouldReply; + } + + private static IpcMessage ReadRequest(KProcess process, ulong messagePtr) + { + const int messageSize = 0x100; + + byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize); + + Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize); + reqDataSpan.Clear(); + + process.CpuMemory.Read(messagePtr, reqDataSpan); + + IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr); + + ArrayPool<byte>.Shared.Return(reqData); + + return request; + } + + private void FillHipcResponse(IpcMessage response, long result) + { + FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty); + } + + private void FillHipcResponse(IpcMessage response, long result, int value) + { + Span<byte> span = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(span, value); + FillHipcResponse(response, result, span); + } + + private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + _responseDataStream.Write(IpcMagic.Sfco); + _responseDataStream.Write(result); + + _responseDataStream.Write(data); + + response.RawData = _responseDataStream.ToArray(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (IpcService service in _sessions.Values) + { + if (service is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + service.DestroyAtExit(); + } + + _sessions.Clear(); + + _requestDataReader.Dispose(); + _requestDataStream.Dispose(); + _responseDataWriter.Dispose(); + _responseDataStream.Dispose(); + + InitDone.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs b/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs new file mode 100644 index 00000000..1b896a27 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + class ServiceAttribute : Attribute + { + public readonly string Name; + public readonly object Parameter; + + public ServiceAttribute(string name, object parameter = null) + { + Name = name; + Parameter = parameter; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs new file mode 100644 index 00000000..4dd344f8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("set:cal")] + class IFactorySettingsServer : IpcService + { + public IFactorySettingsServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs new file mode 100644 index 00000000..3b7e1af2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:fd")] + class IFirmwareDebugSettingsServer : IpcService + { + public IFirmwareDebugSettingsServer(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs new file mode 100644 index 00000000..17e9ec68 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs @@ -0,0 +1,247 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set")] + class ISettingsServer : IpcService + { + public ISettingsServer(ServiceCtx context) { } + + [CommandCmif(0)] + // GetLanguageCode() -> nn::settings::LanguageCode + public ResultCode GetLanguageCode(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.State.DesiredLanguageCode); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetAvailableLanguageCodes() -> (u32, buffer<nn::settings::LanguageCode, 0xa>) + public ResultCode GetAvailableLanguageCodes(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.RecvListBuff[0].Position, + context.Request.RecvListBuff[0].Size, + 0xF); + } + + [CommandCmif(2)] // 4.0.0+ + // MakeLanguageCode(nn::settings::Language language_index) -> nn::settings::LanguageCode + public ResultCode MakeLanguageCode(ServiceCtx context) + { + int languageIndex = context.RequestData.ReadInt32(); + + if ((uint)languageIndex >= (uint)SystemStateMgr.LanguageCodes.Length) + { + return ResultCode.LanguageOutOfRange; + } + + context.ResponseData.Write(SystemStateMgr.GetLanguageCode(languageIndex)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAvailableLanguageCodeCount() -> u32 + public ResultCode GetAvailableLanguageCodeCount(ServiceCtx context) + { + context.ResponseData.Write(Math.Min(SystemStateMgr.LanguageCodes.Length, 0xF)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetRegionCode() -> u32 nn::settings::RegionCode + public ResultCode GetRegionCode(ServiceCtx context) + { + // NOTE: Service mount 0x8000000000000050 savedata and read the region code here. + + RegionCode regionCode = (RegionCode)context.Device.System.State.DesiredRegionCode; + + if (regionCode < RegionCode.Min || regionCode > RegionCode.Max) + { + regionCode = RegionCode.USA; + } + + context.ResponseData.Write((uint)regionCode); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetAvailableLanguageCodes2() -> (u32, buffer<nn::settings::LanguageCode, 6>) + public ResultCode GetAvailableLanguageCodes2(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.ReceiveBuff[0].Position, + context.Request.ReceiveBuff[0].Size, + SystemStateMgr.LanguageCodes.Length); + } + + [CommandCmif(6)] + // GetAvailableLanguageCodeCount2() -> u32 + public ResultCode GetAvailableLanguageCodeCount2(ServiceCtx context) + { + context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length); + + return ResultCode.Success; + } + + [CommandCmif(7)] // 4.0.0+ + // GetKeyCodeMap() -> buffer<nn::kpr::KeyCodeMap, 0x16> + public ResultCode GetKeyCodeMap(ServiceCtx context) + { + return GetKeyCodeMapImpl(context, 1); + } + + [CommandCmif(8)] // 5.0.0+ + // GetQuestFlag() -> bool + public ResultCode GetQuestFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(9)] // 6.0.0+ + // GetKeyCodeMap2() -> buffer<nn::kpr::KeyCodeMap, 0x16> + public ResultCode GetKeyCodeMap2(ServiceCtx context) + { + return GetKeyCodeMapImpl(context, 2); + } + + [CommandCmif(11)] // 10.1.0+ + // GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16> + public ResultCode GetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.ReceiveBuff[0].Size; + + if (deviceNickNameBufferPosition == 0) + { + return ResultCode.NullDeviceNicknameBuffer; + } + + if (deviceNickNameBufferSize != 0x80) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + context.Memory.Write(deviceNickNameBufferPosition, Encoding.ASCII.GetBytes(context.Device.System.State.DeviceNickName + '\0')); + + return ResultCode.Success; + } + + private ResultCode GetKeyCodeMapImpl(ServiceCtx context, int version) + { + if (context.Request.ReceiveBuff[0].Size != 0x1000) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + byte[] keyCodeMap; + + switch ((KeyboardLayout)context.Device.System.State.DesiredKeyboardLayout) + { + case KeyboardLayout.EnglishUs: + + long langCode = context.Device.System.State.DesiredLanguageCode; + + if (langCode == 0x736e61482d687a) // Zh-Hans + { + keyCodeMap = KeyCodeMaps.ChineseSimplified; + } + else if (langCode == 0x746e61482d687a) // Zh-Hant + { + keyCodeMap = KeyCodeMaps.ChineseTraditional; + } + else + { + keyCodeMap = KeyCodeMaps.EnglishUk; + } + + break; + case KeyboardLayout.EnglishUsInternational: + keyCodeMap = KeyCodeMaps.EnglishUsInternational; + break; + case KeyboardLayout.EnglishUk: + keyCodeMap = KeyCodeMaps.EnglishUk; + break; + case KeyboardLayout.French: + keyCodeMap = KeyCodeMaps.French; + break; + case KeyboardLayout.FrenchCa: + keyCodeMap = KeyCodeMaps.FrenchCa; + break; + case KeyboardLayout.Spanish: + keyCodeMap = KeyCodeMaps.Spanish; + break; + case KeyboardLayout.SpanishLatin: + keyCodeMap = KeyCodeMaps.SpanishLatin; + break; + case KeyboardLayout.German: + keyCodeMap = KeyCodeMaps.German; + break; + case KeyboardLayout.Italian: + keyCodeMap = KeyCodeMaps.Italian; + break; + case KeyboardLayout.Portuguese: + keyCodeMap = KeyCodeMaps.Portuguese; + break; + case KeyboardLayout.Russian: + keyCodeMap = KeyCodeMaps.Russian; + break; + case KeyboardLayout.Korean: + keyCodeMap = KeyCodeMaps.Korean; + break; + case KeyboardLayout.ChineseSimplified: + keyCodeMap = KeyCodeMaps.ChineseSimplified; + break; + case KeyboardLayout.ChineseTraditional: + keyCodeMap = KeyCodeMaps.ChineseTraditional; + break; + default: // KeyboardLayout.Default + keyCodeMap = KeyCodeMaps.Default; + break; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, keyCodeMap); + + if (version == 1 && context.Device.System.State.DesiredKeyboardLayout == (long)KeyboardLayout.Default) + { + context.Memory.Write(context.Request.ReceiveBuff[0].Position, (byte)0x01); + } + + return ResultCode.Success; + } + + private ResultCode GetAvailableLanguagesCodesImpl(ServiceCtx context, ulong position, ulong size, int maxSize) + { + int count = (int)(size / 8); + + if (count > maxSize) + { + count = maxSize; + } + + for (int index = 0; index < count; index++) + { + context.Memory.Write(position, SystemStateMgr.GetLanguageCode(index)); + + position += 8; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs new file mode 100644 index 00000000..ef95fa5c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -0,0 +1,348 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:sys")] + class ISystemSettingsServer : IpcService + { + public ISystemSettingsServer(ServiceCtx context) { } + + [CommandCmif(3)] + // GetFirmwareVersion() -> buffer<nn::settings::system::FirmwareVersion, 0x1a, 0x100> + public ResultCode GetFirmwareVersion(ServiceCtx context) + { + return GetFirmwareVersion2(context); + } + + [CommandCmif(4)] + // GetFirmwareVersion2() -> buffer<nn::settings::system::FirmwareVersion, 0x1a, 0x100> + public ResultCode GetFirmwareVersion2(ServiceCtx context) + { + ulong replyPos = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x100L); + + byte[] firmwareData = GetFirmwareData(context.Device); + + if (firmwareData != null) + { + context.Memory.Write(replyPos, firmwareData); + + return ResultCode.Success; + } + + const byte majorFwVersion = 0x03; + const byte minorFwVersion = 0x00; + const byte microFwVersion = 0x00; + const byte unknown = 0x00; //Build? + + const int revisionNumber = 0x0A; + + const string platform = "NX"; + const string unknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; + const string version = "3.0.0"; + const string build = "NintendoSDK Firmware for NX 3.0.0-10.0"; + + // http://switchbrew.org/index.php?title=System_Version_Title + using (MemoryStream ms = new MemoryStream(0x100)) + { + BinaryWriter writer = new BinaryWriter(ms); + + writer.Write(majorFwVersion); + writer.Write(minorFwVersion); + writer.Write(microFwVersion); + writer.Write(unknown); + + writer.Write(revisionNumber); + + writer.Write(Encoding.ASCII.GetBytes(platform)); + + ms.Seek(0x28, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(unknownHex)); + + ms.Seek(0x68, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(version)); + + ms.Seek(0x80, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(build)); + + context.Memory.Write(replyPos, ms.ToArray()); + } + + return ResultCode.Success; + } + + [CommandCmif(23)] + // GetColorSetId() -> i32 + public ResultCode GetColorSetId(ServiceCtx context) + { + context.ResponseData.Write((int)context.Device.System.State.ThemeColor); + + return ResultCode.Success; + } + + [CommandCmif(24)] + // GetColorSetId() -> i32 + public ResultCode SetColorSetId(ServiceCtx context) + { + int colorSetId = context.RequestData.ReadInt32(); + + context.Device.System.State.ThemeColor = (ColorSet)colorSetId; + + return ResultCode.Success; + } + + [CommandCmif(37)] + // GetSettingsItemValueSize(buffer<nn::settings::SettingsName, 0x19>, buffer<nn::settings::SettingsItemKey, 0x19>) -> u64 + public ResultCode GetSettingsItemValueSize(ServiceCtx context) + { + ulong classPos = context.Request.PtrBuff[0].Position; + ulong classSize = context.Request.PtrBuff[0].Size; + + ulong namePos = context.Request.PtrBuff[1].Position; + ulong nameSize = context.Request.PtrBuff[1].Size; + + byte[] classBuffer = new byte[classSize]; + + context.Memory.Read(classPos, classBuffer); + + byte[] nameBuffer = new byte[nameSize]; + + context.Memory.Read(namePos, nameBuffer); + + string askedSetting = Encoding.ASCII.GetString(classBuffer).Trim('\0') + "!" + Encoding.ASCII.GetString(nameBuffer).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + ulong settingSize; + + if (nxSetting is string stringValue) + { + settingSize = (ulong)stringValue.Length + 1; + } + else if (nxSetting is int) + { + settingSize = sizeof(int); + } + else if (nxSetting is bool) + { + settingSize = 1; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.ResponseData.Write(settingSize); + } + + return ResultCode.Success; + } + + [CommandCmif(38)] + // GetSettingsItemValue(buffer<nn::settings::SettingsName, 0x19, 0x48>, buffer<nn::settings::SettingsItemKey, 0x19, 0x48>) -> (u64, buffer<unknown, 6, 0>) + public ResultCode GetSettingsItemValue(ServiceCtx context) + { + ulong classPos = context.Request.PtrBuff[0].Position; + ulong classSize = context.Request.PtrBuff[0].Size; + + ulong namePos = context.Request.PtrBuff[1].Position; + ulong nameSize = context.Request.PtrBuff[1].Size; + + ulong replyPos = context.Request.ReceiveBuff[0].Position; + ulong replySize = context.Request.ReceiveBuff[0].Size; + + byte[] classBuffer = new byte[classSize]; + + context.Memory.Read(classPos, classBuffer); + + byte[] nameBuffer = new byte[nameSize]; + + context.Memory.Read(namePos, nameBuffer); + + string askedSetting = Encoding.ASCII.GetString(classBuffer).Trim('\0') + "!" + Encoding.ASCII.GetString(nameBuffer).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + byte[] settingBuffer = new byte[replySize]; + + if (nxSetting is string stringValue) + { + if ((ulong)(stringValue.Length + 1) > replySize) + { + Logger.Error?.Print(LogClass.ServiceSet, $"{askedSetting} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + + if (nxSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nxSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.Memory.Write(replyPos, settingBuffer); + + Logger.Debug?.Print(LogClass.ServiceSet, $"{askedSetting} set value: {nxSetting} as {nxSetting.GetType()}"); + } + else + { + Logger.Error?.Print(LogClass.ServiceSet, $"{askedSetting} not found!"); + } + + return ResultCode.Success; + } + + [CommandCmif(60)] + // IsUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + // NOTE: When set to true, is automatically synced with the internet. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // GetDebugModeFlag() -> bool + public ResultCode GetDebugModeFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(77)] + // GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16> + public ResultCode GetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.ReceiveBuff[0].Size; + + if (deviceNickNameBufferPosition == 0) + { + return ResultCode.NullDeviceNicknameBuffer; + } + + if (deviceNickNameBufferSize != 0x80) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + context.Memory.Write(deviceNickNameBufferPosition, Encoding.ASCII.GetBytes(context.Device.System.State.DeviceNickName + '\0')); + + return ResultCode.Success; + } + + [CommandCmif(78)] + // SetDeviceNickName(buffer<nn::settings::system::DeviceNickName, 0x15>) + public ResultCode SetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.SendBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.SendBuff[0].Size; + + byte[] deviceNickNameBuffer = new byte[deviceNickNameBufferSize]; + + context.Memory.Read(deviceNickNameBufferPosition, deviceNickNameBuffer); + + context.Device.System.State.DeviceNickName = Encoding.ASCII.GetString(deviceNickNameBuffer); + + return ResultCode.Success; + } + + [CommandCmif(90)] + // GetMiiAuthorId() -> nn::util::Uuid + public ResultCode GetMiiAuthorId(ServiceCtx context) + { + // NOTE: If miiAuthorId is null ResultCode.NullMiiAuthorIdBuffer is returned. + // Doesn't occur in our case. + + context.ResponseData.Write(Mii.Helper.GetDeviceId()); + + return ResultCode.Success; + } + + public byte[] GetFirmwareData(Switch device) + { + const ulong SystemVersionTitleId = 0x0100000000000809; + + string contentPath = device.System.ContentManager.GetInstalledContentPath(SystemVersionTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + + if (string.IsNullOrWhiteSpace(contentPath)) + { + return null; + } + + string firmwareTitlePath = device.FileSystem.SwitchPathToSystemPath(contentPath); + + using(IStorage firmwareStorage = new LocalStorage(firmwareTitlePath, FileAccess.Read)) + { + Nca firmwareContent = new Nca(device.System.KeySet, firmwareStorage); + + if (!firmwareContent.CanOpenSection(NcaSectionType.Data)) + { + return null; + } + + IFileSystem firmwareRomFs = firmwareContent.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + + using var firmwareFile = new UniqueRef<IFile>(); + + Result result = firmwareRomFs.OpenFile(ref firmwareFile.Ref, "/file".ToU8Span(), OpenMode.Read); + if (result.IsFailure()) + { + return null; + } + + result = firmwareFile.Get.GetSize(out long fileSize); + if (result.IsFailure()) + { + return null; + } + + byte[] data = new byte[fileSize]; + + result = firmwareFile.Get.Read(out _, 0, data); + if (result.IsFailure()) + { + return null; + } + + return data; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs b/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs new file mode 100644 index 00000000..67d1ac92 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs @@ -0,0 +1,4849 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + class KeyCodeMaps + { + public static byte[] Default = + { + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x61, 0x30, + 0x61, 0x30, 0xc1, 0x30, 0xc1, 0x30, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, + 0x53, 0x30, 0x53, 0x30, 0xb3, 0x30, 0xb3, 0x30, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x5d, 0x30, 0x5d, 0x30, 0xbd, 0x30, 0xbd, 0x30, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x57, 0x30, 0x57, 0x30, 0xb7, 0x30, 0xb7, 0x30, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x44, 0x30, 0x43, 0x30, 0xa4, 0x30, + 0xa3, 0x30, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, 0x6f, 0x30, 0x6f, 0x30, + 0xcf, 0x30, 0xcf, 0x30, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x4d, 0x30, + 0x4d, 0x30, 0xad, 0x30, 0xad, 0x30, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, + 0x4f, 0x30, 0x4f, 0x30, 0xaf, 0x30, 0xaf, 0x30, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x6b, 0x30, 0x6b, 0x30, 0xcb, 0x30, 0xcb, 0x30, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x7e, 0x30, 0x7e, 0x30, 0xde, 0x30, 0xde, 0x30, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x6e, 0x30, 0x6e, 0x30, 0xce, 0x30, + 0xce, 0x30, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, 0x8a, 0x30, 0x8a, 0x30, + 0xea, 0x30, 0xea, 0x30, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x82, 0x30, + 0x82, 0x30, 0xe2, 0x30, 0xe2, 0x30, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, + 0x7f, 0x30, 0x7f, 0x30, 0xdf, 0x30, 0xdf, 0x30, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x89, 0x30, 0x89, 0x30, 0xe9, 0x30, 0xe9, 0x30, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x5b, 0x30, 0x5b, 0x30, 0xbb, 0x30, 0xbb, 0x30, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x5f, 0x30, 0x5f, 0x30, 0xbf, 0x30, + 0xbf, 0x30, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, 0x59, 0x30, 0x59, 0x30, + 0xb9, 0x30, 0xb9, 0x30, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x68, 0x30, + 0x68, 0x30, 0xc8, 0x30, 0xc8, 0x30, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, + 0x4b, 0x30, 0x4b, 0x30, 0xab, 0x30, 0xab, 0x30, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x6a, 0x30, 0x6a, 0x30, 0xca, 0x30, 0xca, 0x30, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x72, 0x30, 0x72, 0x30, 0xd2, 0x30, 0xd2, 0x30, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x66, 0x30, 0x66, 0x30, 0xc6, 0x30, + 0xc6, 0x30, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, 0x55, 0x30, 0x55, 0x30, + 0xb5, 0x30, 0xb5, 0x30, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x93, 0x30, + 0x93, 0x30, 0xf3, 0x30, 0xf3, 0x30, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, + 0x64, 0x30, 0x63, 0x30, 0xc4, 0x30, 0xc3, 0x30, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x6c, 0x30, 0x6c, 0x30, 0xcc, 0x30, 0xcc, 0x30, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x75, 0x30, 0x75, 0x30, 0xd5, 0x30, 0xd5, 0x30, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x42, 0x30, 0x41, 0x30, 0xa2, 0x30, + 0xa1, 0x30, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, 0x46, 0x30, 0x45, 0x30, + 0xa6, 0x30, 0xa5, 0x30, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x48, 0x30, + 0x47, 0x30, 0xa8, 0x30, 0xa7, 0x30, 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, + 0x4a, 0x30, 0x49, 0x30, 0xaa, 0x30, 0xa9, 0x30, 0x00, 0x10, 0x37, 0x00, + 0x27, 0x00, 0x84, 0x30, 0x83, 0x30, 0xe4, 0x30, 0xe3, 0x30, 0x00, 0x10, + 0x38, 0x00, 0x28, 0x00, 0x86, 0x30, 0x85, 0x30, 0xe6, 0x30, 0xe5, 0x30, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x88, 0x30, 0x87, 0x30, 0xe8, 0x30, + 0xe7, 0x30, 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x8f, 0x30, 0x92, 0x30, + 0xef, 0x30, 0xf2, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x3d, 0x00, 0x7b, 0x30, 0x7b, 0x30, + 0xdb, 0x30, 0xdb, 0x30, 0x00, 0x10, 0x5e, 0x00, 0x7e, 0x00, 0x78, 0x30, + 0x78, 0x30, 0xd8, 0x30, 0xd8, 0x30, 0x00, 0x10, 0x40, 0x00, 0x60, 0x00, + 0x9e, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x00, 0x10, 0x5b, 0x00, + 0x7b, 0x00, 0x9f, 0xff, 0x62, 0xff, 0x9f, 0xff, 0x62, 0xff, 0x00, 0x10, + 0x5d, 0x00, 0x7d, 0x00, 0x80, 0x30, 0x63, 0xff, 0xe0, 0x30, 0x63, 0xff, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x80, 0x30, 0x63, 0xff, 0xe0, 0x30, + 0x63, 0xff, 0x00, 0x10, 0x3b, 0x00, 0x2b, 0x00, 0x8c, 0x30, 0x8c, 0x30, + 0xec, 0x30, 0xec, 0x30, 0x00, 0x10, 0x3a, 0x00, 0x2a, 0x00, 0x51, 0x30, + 0x51, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3c, 0x00, 0x6d, 0x30, 0x64, 0xff, 0xcd, 0x30, 0x64, 0xff, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x8b, 0x30, 0x61, 0xff, 0xeb, 0x30, 0x61, 0xff, + 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x81, 0x30, 0x65, 0xff, 0xe1, 0x30, + 0x65, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, + 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, + 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x5f, 0x00, 0x8d, 0x30, 0x8d, 0x30, + 0xed, 0x30, 0xed, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, + 0x70, 0xff, 0x70, 0xff, 0x70, 0xff, 0x70, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] EnglishUsInternational = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x61, 0x00, 0x41, 0x00, 0xe1, 0x00, + 0xc1, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0xa9, 0x00, 0xa2, 0x00, 0x03, 0x10, + 0x64, 0x00, 0x44, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0x03, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x69, 0x00, 0x49, 0x00, 0xed, 0x00, 0xcd, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0xf8, 0x00, 0xd8, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x00, 0x00, 0x03, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0xf1, 0x00, 0xd1, 0x00, + 0x03, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0xf3, 0x00, 0xd3, 0x00, 0x03, 0x10, + 0x70, 0x00, 0x50, 0x00, 0xf6, 0x00, 0xd6, 0x00, 0x03, 0x10, 0x71, 0x00, + 0x51, 0x00, 0xe4, 0x00, 0xc4, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0xae, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0xdf, 0x00, + 0xa7, 0x00, 0x03, 0x10, 0x74, 0x00, 0x54, 0x00, 0xfe, 0x00, 0xde, 0x00, + 0x03, 0x10, 0x75, 0x00, 0x55, 0x00, 0xfa, 0x00, 0xda, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x77, 0x00, + 0x57, 0x00, 0xe5, 0x00, 0xc5, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x79, 0x00, 0x59, 0x00, 0xfc, 0x00, + 0xdc, 0x00, 0x03, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0xe6, 0x00, 0xc6, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0xa1, 0x00, 0xb9, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0xa4, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x02, 0x03, 0xbc, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x18, 0x20, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x19, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0xa5, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0xd7, 0x00, + 0xf7, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0xab, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0xb6, 0x00, 0xb0, 0x00, 0x00, 0x10, 0x0d, 0x03, 0x0e, 0x03, 0xb4, 0x00, + 0xa8, 0x00, 0x00, 0x10, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0xe7, 0x00, 0xc7, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] EnglishUk = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x61, 0x00, 0x41, 0x00, 0xe1, 0x00, + 0xc1, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x69, 0x00, 0x49, 0x00, 0xed, 0x00, 0xcd, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0xf3, 0x00, 0xd3, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x75, 0x00, 0x55, 0x00, 0xfa, 0x00, 0xda, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0xac, 0x20, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x23, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x23, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x60, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] French = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2c, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x26, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x01, 0x10, 0xe9, 0x00, 0x32, 0x00, 0x03, 0x03, + 0x01, 0x10, 0x22, 0x00, 0x33, 0x00, 0x23, 0x00, 0x01, 0x10, 0x27, 0x00, + 0x34, 0x00, 0x7b, 0x00, 0x01, 0x10, 0x28, 0x00, 0x35, 0x00, 0x5b, 0x00, + 0x01, 0x10, 0x2d, 0x00, 0x36, 0x00, 0x7c, 0x00, 0x01, 0x10, 0xe8, 0x00, + 0x37, 0x00, 0x00, 0x03, 0x01, 0x10, 0x5f, 0x00, 0x38, 0x00, 0x5c, 0x00, + 0x01, 0x10, 0xe7, 0x00, 0x39, 0x00, 0x5e, 0x00, 0x01, 0x10, 0xe0, 0x00, + 0x30, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x01, 0x10, 0x29, 0x00, + 0xb0, 0x00, 0x5d, 0x00, 0x01, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x7d, 0x00, + 0x01, 0x10, 0x02, 0x03, 0x08, 0x03, 0x00, 0x00, 0x01, 0x10, 0x24, 0x00, + 0xa3, 0x00, 0xa4, 0x00, 0x01, 0x10, 0x2a, 0x00, 0xb5, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x2a, 0x00, 0xb5, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, + 0x4d, 0x00, 0x00, 0x00, 0x01, 0x10, 0xf9, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x3b, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x3a, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x21, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] FrenchCa = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0xa7, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0xb6, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0xb1, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x2f, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0xa2, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xa4, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x3f, 0x00, 0xac, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x26, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x38, 0x00, 0x2a, 0x00, 0xb2, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0xb3, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x29, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x5f, 0x00, 0xbd, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0xbe, 0x00, + 0x00, 0x10, 0x02, 0x03, 0x02, 0x03, 0x5b, 0x00, 0x00, 0x10, 0x27, 0x03, + 0x08, 0x03, 0x5d, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7d, 0x00, + 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7d, 0x00, 0x00, 0x10, 0x3b, 0x00, + 0x3a, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x03, 0x7b, 0x00, + 0x00, 0x10, 0x23, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x27, 0x00, 0xaf, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x2e, 0x00, 0x2d, 0x00, + 0x01, 0x10, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0xab, 0x00, 0xbb, 0x00, 0xb0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] Spanish = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0xb7, 0x00, 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x03, 0x03, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0xac, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa1, 0x00, 0xbf, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x03, 0x02, 0x03, 0x5b, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x5d, 0x00, 0x01, 0x10, 0xe7, 0x00, 0xc7, 0x00, 0x7d, 0x00, + 0x01, 0x10, 0xe7, 0x00, 0xc7, 0x00, 0x7d, 0x00, 0x01, 0x10, 0xf1, 0x00, + 0xd1, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x08, 0x03, 0x7b, 0x00, + 0x00, 0x10, 0xba, 0x00, 0xaa, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] SpanishLatin = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x40, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x5c, 0x00, 0x00, 0x10, 0xbf, 0x00, 0xa1, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x01, 0x03, 0x08, 0x03, 0x00, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x7d, 0x00, 0x5d, 0x00, 0x00, 0x03, + 0x00, 0x10, 0x7d, 0x00, 0x5d, 0x00, 0x00, 0x03, 0x01, 0x10, 0xf1, 0x00, + 0xd1, 0x00, 0x00, 0x00, 0x00, 0x10, 0x7b, 0x00, 0x5b, 0x00, 0x02, 0x03, + 0x00, 0x10, 0x7c, 0x00, 0xb0, 0x00, 0xac, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] German = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x40, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, 0x01, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x10, 0x32, 0x00, 0x22, 0x00, 0xb2, 0x00, + 0x01, 0x10, 0x33, 0x00, 0xa7, 0x00, 0xb3, 0x00, 0x01, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x01, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x7b, 0x00, 0x01, 0x10, 0x38, 0x00, 0x28, 0x00, 0x5b, 0x00, + 0x01, 0x10, 0x39, 0x00, 0x29, 0x00, 0x5d, 0x00, 0x01, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x01, 0x10, 0xdf, 0x00, + 0x3f, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, + 0x01, 0x10, 0xfc, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x7e, 0x00, 0x01, 0x10, 0x23, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x23, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x10, 0xf6, 0x00, + 0xd6, 0x00, 0x00, 0x00, 0x01, 0x10, 0xe4, 0x00, 0xc4, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x02, 0x03, 0xb0, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] Italian = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xac, 0x20, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xec, 0x00, 0x5e, 0x00, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x10, 0xe8, 0x00, 0xe9, 0x00, 0x5b, 0x00, 0x7b, 0x00, + 0x00, 0x10, 0x2b, 0x00, 0x2a, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0xf9, 0x00, 0xa7, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf9, 0x00, + 0xa7, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf2, 0x00, 0xe7, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0xe0, 0x00, 0xb0, 0x00, 0x23, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2c, 0x00, + 0x00, 0x00, 0x2c, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] Portuguese = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0xa7, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x5b, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x5d, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x10, 0xab, 0x00, 0xbb, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2b, 0x00, 0x2a, 0x00, 0x08, 0x03, 0x00, 0x10, 0x01, 0x03, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x03, 0x03, 0x02, 0x03, 0x00, 0x00, + 0x00, 0x10, 0x03, 0x03, 0x02, 0x03, 0x00, 0x00, 0x01, 0x10, 0xe7, 0x00, + 0xc7, 0x00, 0x00, 0x00, 0x00, 0x10, 0xba, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] Russian = + { + 0x09, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x10, 0x61, 0x00, 0x41, 0x00, 0x44, 0x04, + 0x24, 0x04, 0x11, 0x10, 0x62, 0x00, 0x42, 0x00, 0x38, 0x04, 0x18, 0x04, + 0x11, 0x10, 0x63, 0x00, 0x43, 0x00, 0x41, 0x04, 0x21, 0x04, 0x11, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x32, 0x04, 0x12, 0x04, 0x11, 0x10, 0x65, 0x00, + 0x45, 0x00, 0x43, 0x04, 0x23, 0x04, 0x11, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x30, 0x04, 0x10, 0x04, 0x11, 0x10, 0x67, 0x00, 0x47, 0x00, 0x3f, 0x04, + 0x1f, 0x04, 0x11, 0x10, 0x68, 0x00, 0x48, 0x00, 0x40, 0x04, 0x20, 0x04, + 0x11, 0x10, 0x69, 0x00, 0x49, 0x00, 0x48, 0x04, 0x28, 0x04, 0x11, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x3e, 0x04, 0x1e, 0x04, 0x11, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x3b, 0x04, 0x1b, 0x04, 0x11, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x34, 0x04, 0x14, 0x04, 0x11, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x4c, 0x04, + 0x2c, 0x04, 0x11, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x42, 0x04, 0x22, 0x04, + 0x11, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x49, 0x04, 0x29, 0x04, 0x11, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x37, 0x04, 0x17, 0x04, 0x11, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x39, 0x04, 0x19, 0x04, 0x11, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x3a, 0x04, 0x1a, 0x04, 0x11, 0x10, 0x73, 0x00, 0x53, 0x00, 0x4b, 0x04, + 0x2b, 0x04, 0x11, 0x10, 0x74, 0x00, 0x54, 0x00, 0x35, 0x04, 0x15, 0x04, + 0x11, 0x10, 0x75, 0x00, 0x55, 0x00, 0x33, 0x04, 0x13, 0x04, 0x11, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x3c, 0x04, 0x1c, 0x04, 0x11, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x46, 0x04, 0x26, 0x04, 0x11, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x47, 0x04, 0x27, 0x04, 0x11, 0x10, 0x79, 0x00, 0x59, 0x00, 0x3d, 0x04, + 0x1d, 0x04, 0x11, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x4f, 0x04, 0x2f, 0x04, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x32, 0x00, 0x22, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0x33, 0x00, 0x16, 0x21, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x34, 0x00, 0x3b, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x35, 0x00, + 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x36, 0x00, 0x3a, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x37, 0x00, 0x3f, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x10, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x45, 0x04, 0x25, 0x04, + 0x10, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x4a, 0x04, 0x2a, 0x04, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x2f, 0x00, 0x10, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x36, 0x04, 0x16, 0x04, 0x10, 0x10, 0x27, 0x00, 0x22, 0x00, 0x4d, 0x04, + 0x2d, 0x04, 0x10, 0x10, 0x60, 0x00, 0x7e, 0x00, 0x51, 0x04, 0x01, 0x04, + 0x10, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x31, 0x04, 0x11, 0x04, 0x10, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x4e, 0x04, 0x2e, 0x04, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x2e, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2c, 0x00, + 0x00, 0x00, 0x2c, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] Korean = + { + 0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x41, 0x31, + 0x41, 0x31, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x60, 0x31, 0x60, 0x31, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x4a, 0x31, 0x4a, 0x31, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x47, 0x31, 0x47, 0x31, 0x01, 0x10, 0x65, 0x00, + 0x45, 0x00, 0x37, 0x31, 0x38, 0x31, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x39, 0x31, 0x39, 0x31, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x4e, 0x31, + 0x4e, 0x31, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x57, 0x31, 0x57, 0x31, + 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x51, 0x31, 0x51, 0x31, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x53, 0x31, 0x53, 0x31, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x4f, 0x31, 0x4f, 0x31, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x63, 0x31, 0x63, 0x31, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x61, 0x31, + 0x61, 0x31, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x5c, 0x31, 0x5c, 0x31, + 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x50, 0x31, 0x52, 0x31, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x54, 0x31, 0x56, 0x31, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x42, 0x31, 0x43, 0x31, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x31, 0x31, 0x32, 0x31, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x34, 0x31, + 0x34, 0x31, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x45, 0x31, 0x46, 0x31, + 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x55, 0x31, 0x55, 0x31, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x4d, 0x31, 0x4d, 0x31, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x48, 0x31, 0x49, 0x31, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x4c, 0x31, 0x4c, 0x31, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x5b, 0x31, + 0x5b, 0x31, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x4b, 0x31, 0x4b, 0x31, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x32, 0x00, 0x40, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0x33, 0x00, 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x35, 0x00, + 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x36, 0x00, 0x5e, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x5b, 0x00, 0x7b, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x27, 0x00, + 0x22, 0x00, 0x00, 0x10, 0x60, 0x00, 0x7e, 0x00, 0x60, 0x00, 0x7e, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x2f, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] ChineseSimplified = + { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x01, 0x10, + 0x62, 0x00, 0x42, 0x00, 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x01, 0x10, + 0x66, 0x00, 0x46, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x01, 0x10, + 0x68, 0x00, 0x48, 0x00, 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x01, 0x10, + 0x6c, 0x00, 0x4c, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x01, 0x10, + 0x6e, 0x00, 0x4e, 0x00, 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x01, 0x10, + 0x72, 0x00, 0x52, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x01, 0x10, + 0x74, 0x00, 0x54, 0x00, 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x01, 0x10, + 0x78, 0x00, 0x58, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x01, 0x10, + 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x00, 0x10, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x10, + 0x36, 0x00, 0x5e, 0x00, 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x10, + 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x00, 0x10, + 0x60, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + public static byte[] ChineseTraditional = + { + 0x61, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x07, 0x31, + 0x00, 0x00, 0xe5, 0x65, 0x00, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, + 0x16, 0x31, 0x00, 0x00, 0x08, 0x67, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x0f, 0x31, 0x00, 0x00, 0xd1, 0x91, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x0e, 0x31, 0x00, 0x00, 0x28, 0x67, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x0d, 0x31, 0x00, 0x00, 0x34, 0x6c, + 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, 0x11, 0x31, 0x00, 0x00, + 0x6b, 0x70, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x15, 0x31, + 0x00, 0x00, 0x1f, 0x57, 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, + 0x18, 0x31, 0x00, 0x00, 0xf9, 0x7a, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x1b, 0x31, 0x00, 0x00, 0x08, 0x62, 0x00, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x28, 0x31, 0x00, 0x00, 0x41, 0x53, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x1c, 0x31, 0x00, 0x00, 0x27, 0x59, + 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, 0x20, 0x31, 0x00, 0x00, + 0x2d, 0x4e, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x29, 0x31, + 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, + 0x19, 0x31, 0x00, 0x00, 0x13, 0x5f, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x1f, 0x31, 0x00, 0x00, 0xba, 0x4e, 0x00, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x23, 0x31, 0x00, 0x00, 0xc3, 0x5f, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x06, 0x31, 0x00, 0x00, 0x4b, 0x62, + 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, 0x10, 0x31, 0x00, 0x00, + 0xe3, 0x53, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x0b, 0x31, + 0x00, 0x00, 0x38, 0x5c, 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, + 0x14, 0x31, 0x00, 0x00, 0xff, 0x5e, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x27, 0x31, 0x00, 0x00, 0x71, 0x5c, 0x00, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x12, 0x31, 0x00, 0x00, 0x73, 0x59, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x0a, 0x31, 0x00, 0x00, 0x30, 0x75, + 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, 0x0c, 0x31, 0x00, 0x00, + 0xe3, 0x96, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x17, 0x31, + 0x00, 0x00, 0x5c, 0x53, 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, + 0x08, 0x31, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x05, 0x31, 0x00, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x09, 0x31, 0x00, 0x00, 0x32, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0xc7, 0x02, 0x00, 0x00, 0x33, 0x00, + 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, 0xcb, 0x02, 0x00, 0x00, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x13, 0x31, + 0x00, 0x00, 0x35, 0x00, 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, + 0xca, 0x02, 0x00, 0x00, 0x36, 0x00, 0x5e, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x26, 0x00, 0xd9, 0x02, 0x00, 0x00, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x1a, 0x31, 0x00, 0x00, 0x38, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0x1e, 0x31, 0x00, 0x00, 0x39, 0x00, + 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, 0x22, 0x31, 0x00, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x26, 0x31, 0x00, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, + 0x5b, 0x00, 0x7b, 0x00, 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x5d, 0x00, + 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, + 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, 0x24, 0x31, 0x00, 0x00, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x27, 0x00, + 0x22, 0x00, 0x27, 0x00, 0x22, 0x00, 0x00, 0x10, 0x60, 0x00, 0x7e, 0x00, + 0x60, 0x00, 0x7e, 0x00, 0x60, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3c, 0x00, 0x1d, 0x31, 0x00, 0x00, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x21, 0x31, 0x00, 0x00, 0x2e, 0x00, 0x3e, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x25, 0x31, 0x00, 0x00, 0x2f, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, + 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, + 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + }; +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs new file mode 100644 index 00000000..e5f218a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs @@ -0,0 +1,1712 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + static class NxSettings + { + // Generated automatically from a Switch 3.0 config file (Tid: 0100000000000818). + public static Dictionary<string, object> Settings = new Dictionary<string, object> + { + { "account!na_required_for_network_service", true }, + { "account.daemon!background_awaking_periodicity", 10800 }, + { "account.daemon!schedule_periodicity", 3600 }, + { "account.daemon!profile_sync_interval", 18000 }, + { "account.daemon!na_info_refresh_interval", 46800 }, + { "am.display!transition_layer_enabled", true }, + { "am.gpu!gpu_scheduling_enabled", true }, + { "am.gpu!gpu_scheduling_frame_time_us", 116666 }, + { "am.gpu!gpu_scheduling_fg_app_us", 116166 }, + { "am.gpu!gpu_scheduling_bg_app_us", 104500 }, + { "am.gpu!gpu_scheduling_oa_us", 500 }, + { "am.gpu!gpu_scheduling_fg_sa_us", 11666 }, + { "am.gpu!gpu_scheduling_bg_sa_us", 0 }, + { "am.gpu!gpu_scheduling_fg_la_us", 11666 }, + { "am.gpu!gpu_scheduling_partial_fg_la_us", 2000 }, + { "am.gpu!gpu_scheduling_bg_la_us", 0 }, + { "audio!audren_log_enabled", false }, + { "audio!audout_log_enabled", false }, + { "audio!audin_log_enabled", false }, + { "audio!hwopus_log_enabled", false }, + { "audio!adsp_log_enabled", false }, + { "audio!suspend_for_debugger_enabled", false }, + { "audio!uac_speaker_enabled", false }, + { "bgtc!enable_halfawake", 1 }, + { "bgtc!enable_battery_saver", 1 }, + { "bgtc!leaving_halfawake_margin", 3 }, + { "bgtc!battery_threshold_save", 20 }, + { "bgtc!battery_threshold_stop", 20 }, + { "bgtc!minimum_interval_normal", 1800 }, + { "bgtc!minimum_interval_save", 86400 }, + { "boot!force_maintenance", false }, + { "capsrv!screenshot_layerstack", "screenshot" }, + { "capsrv!enable_album_screenshot_filedata_verification", true }, + { "devmenu!enable_application_update", true }, + { "devmenu!enable_exhibition_mode", false }, + { "eclct!analytics_override", false }, + { "eclct!analytics_pollperiod", 86400 }, + { "err!applet_auto_close", false }, + { "friends!background_processing", true }, + { "htc!disconnection_emulation", false }, + { "idle!dim_level_percent_lcd", 10 }, + { "idle!dim_level_percent_tv", 70 }, + { "lbl!force_disable_als", false }, + { "lm!enable_sd_card_logging", false }, + { "lm!sd_card_log_output_directory", "NxBinLogs" }, + { "mii!is_db_test_mode_enabled", false }, + { "news!system_version", 2 }, + { "nfp!not_locked_tag", true }, + { "nfp!play_report", false }, + { "nifm!is_communication_control_enabled_for_test", false }, + { "nifm!connection_test_timeout", 45000 }, + { "nifm!apply_config_timeout", 30000 }, + { "nifm!ethernet_adapter_standby_time", 10000 }, + { "nim.install!prefer_delta_evenif_inefficient", false }, + { "nim.install!apply_delta_stress_storage", 0 }, + { "ns.notification!retry_interval", 60 }, + { "ns.notification!enable_network_update", true }, + { "ns.notification!enable_download_task_list", true }, + { "ns.notification!enable_version_list", true }, + { "ns.notification!enable_random_wait", true }, + { "ns.notification!debug_waiting_limit", 0 }, + { "ns.notification!enable_request_on_cold_boot", true }, + { "ns.sdcard!mount_sdcard", true }, + { "ns.sdcard!compare_sdcard", 0 }, + { "ns.gamecard!mount_gamecard_result_value", 0 }, + { "ns.gamecard!try_gamecard_access_result_value", 0 }, + { "nv!00008600", "" }, + { "nv!0007b25e", "" }, + { "nv!0083e1", "" }, + { "nv!01621887", "" }, + { "nv!03134743", "" }, + { "nv!0356afd0", "" }, + { "nv!0356afd1", "" }, + { "nv!0356afd2", "" }, + { "nv!0356afd3", "" }, + { "nv!094313", "" }, + { "nv!0x04dc09", "" }, + { "nv!0x111133", "" }, + { "nv!0x1aa483", "" }, + { "nv!0x1cb1cf", "" }, + { "nv!0x1cb1d0", "" }, + { "nv!0x1e3221", "" }, + { "nv!0x300fc8", "" }, + { "nv!0x301fc8", "" }, + { "nv!0x302fc8", "" }, + { "nv!0x3eec59", "" }, + { "nv!0x46b3ed", "" }, + { "nv!0x523dc0", "" }, + { "nv!0x523dc1", "" }, + { "nv!0x523dc2", "" }, + { "nv!0x523dc3", "" }, + { "nv!0x523dc4", "" }, + { "nv!0x523dc5", "" }, + { "nv!0x523dc6", "" }, + { "nv!0x523dd0", "" }, + { "nv!0x523dd1", "" }, + { "nv!0x523dd3", "" }, + { "nv!0x5344bb", "" }, + { "nv!0x555237", "" }, + { "nv!0x58a234", "" }, + { "nv!0x7b4428", "" }, + { "nv!0x923dc0", "" }, + { "nv!0x923dc1", "" }, + { "nv!0x923dc2", "" }, + { "nv!0x923dc3", "" }, + { "nv!0x923dc4", "" }, + { "nv!0x923dd3", "" }, + { "nv!0x9abdc5", "" }, + { "nv!0x9abdc6", "" }, + { "nv!0xaaa36c", "" }, + { "nv!0xb09da0", "" }, + { "nv!0xb09da1", "" }, + { "nv!0xb09da2", "" }, + { "nv!0xb09da3", "" }, + { "nv!0xb09da4", "" }, + { "nv!0xb09da5", "" }, + { "nv!0xb0b348", "" }, + { "nv!0xb0b349", "" }, + { "nv!0xbb558f", "" }, + { "nv!0xbd10fb", "" }, + { "nv!0xc32ad3", "" }, + { "nv!0xce2348", "" }, + { "nv!0xcfd81f", "" }, + { "nv!0xe0036b", "" }, + { "nv!0xe01f2d", "" }, + { "nv!0xe17212", "" }, + { "nv!0xeae966", "" }, + { "nv!0xed4f82", "" }, + { "nv!0xf12335", "" }, + { "nv!0xf12336", "" }, + { "nv!10261989", "" }, + { "nv!1042d483", "" }, + { "nv!10572898", "" }, + { "nv!115631", "" }, + { "nv!12950094", "" }, + { "nv!1314f311", "" }, + { "nv!1314f312", "" }, + { "nv!13279512", "" }, + { "nv!13813496", "" }, + { "nv!14507179", "" }, + { "nv!15694569", "" }, + { "nv!16936964", "" }, + { "nv!17aa230c", "" }, + { "nv!182054", "" }, + { "nv!18273275", "" }, + { "nv!18273276", "" }, + { "nv!1854d03b", "" }, + { "nv!18add00d", "" }, + { "nv!19156670", "" }, + { "nv!19286545", "" }, + { "nv!1a298e9f", "" }, + { "nv!1acf43fe", "" }, + { "nv!1bda43fe", "" }, + { "nv!1c3b92", "" }, + { "nv!21509920", "" }, + { "nv!215323457", "" }, + { "nv!2165ad", "" }, + { "nv!2165ae", "" }, + { "nv!21be9c", "" }, + { "nv!233264316", "" }, + { "nv!234557580", "" }, + { "nv!23cd0e", "" }, + { "nv!24189123", "" }, + { "nv!2443266", "" }, + { "nv!25025519", "" }, + { "nv!255e39", "" }, + { "nv!2583364", "" }, + { "nv!2888c1", "" }, + { "nv!28ca3e", "" }, + { "nv!29871243", "" }, + { "nv!2a1f64", "" }, + { "nv!2dc432", "" }, + { "nv!2de437", "" }, + { "nv!2f3bb89c", "" }, + { "nv!2fd652", "" }, + { "nv!3001ac", "" }, + { "nv!31298772", "" }, + { "nv!313233", "" }, + { "nv!31f7d603", "" }, + { "nv!320ce4", "" }, + { "nv!32153248", "" }, + { "nv!32153249", "" }, + { "nv!335bca", "" }, + { "nv!342abb", "" }, + { "nv!34dfe6", "" }, + { "nv!34dfe7", "" }, + { "nv!34dfe8", "" }, + { "nv!34dfe9", "" }, + { "nv!35201578", "" }, + { "nv!359278", "" }, + { "nv!37f53a", "" }, + { "nv!38144972", "" }, + { "nv!38542646", "" }, + { "nv!3b74c9", "" }, + { "nv!3c136f", "" }, + { "nv!3cf72823", "" }, + { "nv!3d7af029", "" }, + { "nv!3ff34782", "" }, + { "nv!4129618", "" }, + { "nv!4189fac3", "" }, + { "nv!420bd4", "" }, + { "nv!42a699", "" }, + { "nv!441369", "" }, + { "nv!4458713e", "" }, + { "nv!4554b6", "" }, + { "nv!457425", "" }, + { "nv!4603b207", "" }, + { "nv!46574957", "" }, + { "nv!46574958", "" }, + { "nv!46813529", "" }, + { "nv!46f1e13d", "" }, + { "nv!47534c43", "" }, + { "nv!48550336", "" }, + { "nv!48576893", "" }, + { "nv!48576894", "" }, + { "nv!4889ac02", "" }, + { "nv!49005740", "" }, + { "nv!49867584", "" }, + { "nv!49960973", "" }, + { "nv!4a5341", "" }, + { "nv!4f4e48", "" }, + { "nv!4f8a0a", "" }, + { "nv!50299698", "" }, + { "nv!50299699", "" }, + { "nv!50361291", "" }, + { "nv!5242ae", "" }, + { "nv!53d30c", "" }, + { "nv!56347a", "" }, + { "nv!563a95f1", "" }, + { "nv!573823", "" }, + { "nv!58027529", "" }, + { "nv!5d2d63", "" }, + { "nv!5f7e3b", "" }, + { "nv!60461793", "" }, + { "nv!60d355", "" }, + { "nv!616627aa", "" }, + { "nv!62317182", "" }, + { "nv!6253fa2e", "" }, + { "nv!64100768", "" }, + { "nv!64100769", "" }, + { "nv!64100770", "" }, + { "nv!647395", "" }, + { "nv!66543234", "" }, + { "nv!67674763", "" }, + { "nv!67739784", "" }, + { "nv!68fb9c", "" }, + { "nv!69801276", "" }, + { "nv!6af9fa2f", "" }, + { "nv!6af9fa3f", "" }, + { "nv!6af9fa4f", "" }, + { "nv!6bd8c7", "" }, + { "nv!6c7691", "" }, + { "nv!6d4296ce", "" }, + { "nv!6dd7e7", "" }, + { "nv!6dd7e8", "" }, + { "nv!6fe11ec1", "" }, + { "nv!716511763", "" }, + { "nv!72504593", "" }, + { "nv!73304097", "" }, + { "nv!73314098", "" }, + { "nv!74095213", "" }, + { "nv!74095213a", "" }, + { "nv!74095213b", "" }, + { "nv!74095214", "" }, + { "nv!748f9649", "" }, + { "nv!75494732", "" }, + { "nv!78452832", "" }, + { "nv!784561", "" }, + { "nv!78e16b9c", "" }, + { "nv!79251225", "" }, + { "nv!7c128b", "" }, + { "nv!7ccd93", "" }, + { "nv!7df8d1", "" }, + { "nv!800c2310", "" }, + { "nv!80546710", "" }, + { "nv!80772310", "" }, + { "nv!808ee280", "" }, + { "nv!81131154", "" }, + { "nv!81274457", "" }, + { "nv!8292291f", "" }, + { "nv!83498426", "" }, + { "nv!84993794", "" }, + { "nv!84995585", "" }, + { "nv!84a0a0", "" }, + { "nv!852142", "" }, + { "nv!85612309", "" }, + { "nv!85612310", "" }, + { "nv!85612311", "" }, + { "nv!85612312", "" }, + { "nv!8623ff27", "" }, + { "nv!87364952", "" }, + { "nv!87f6275666", "" }, + { "nv!886748", "" }, + { "nv!89894423", "" }, + { "nv!8ad8a75", "" }, + { "nv!8ad8ad00", "" }, + { "nv!8bb815", "" }, + { "nv!8bb817", "" }, + { "nv!8bb818", "" }, + { "nv!8bb819", "" }, + { "nv!8e640cd1", "" }, + { "nv!8f34971a", "" }, + { "nv!8f773984", "" }, + { "nv!8f7a7d", "" }, + { "nv!902486209", "" }, + { "nv!90482571", "" }, + { "nv!91214835", "" }, + { "nv!912848290", "" }, + { "nv!915e56", "" }, + { "nv!92179063", "" }, + { "nv!92179064", "" }, + { "nv!92179065", "" }, + { "nv!92179066", "" }, + { "nv!92350358", "" }, + { "nv!92809063", "" }, + { "nv!92809064", "" }, + { "nv!92809065", "" }, + { "nv!92809066", "" }, + { "nv!92920143", "" }, + { "nv!93a89b12", "" }, + { "nv!93a89c0b", "" }, + { "nv!94812574", "" }, + { "nv!95282304", "" }, + { "nv!95394027", "" }, + { "nv!959b1f", "" }, + { "nv!9638af", "" }, + { "nv!96fd59", "" }, + { "nv!97f6275666", "" }, + { "nv!97f6275667", "" }, + { "nv!97f6275668", "" }, + { "nv!97f6275669", "" }, + { "nv!97f627566a", "" }, + { "nv!97f627566b", "" }, + { "nv!97f627566d", "" }, + { "nv!97f627566e", "" }, + { "nv!97f627566f", "" }, + { "nv!97f6275670", "" }, + { "nv!97f6275671", "" }, + { "nv!97f727566e", "" }, + { "nv!98480775", "" }, + { "nv!98480776", "" }, + { "nv!98480777", "" }, + { "nv!992431", "" }, + { "nv!9aa29065", "" }, + { "nv!9af32c", "" }, + { "nv!9af32d", "" }, + { "nv!9af32e", "" }, + { "nv!9c108b71", "" }, + { "nv!9f279065", "" }, + { "nv!a01bc728", "" }, + { "nv!a13b46c80", "" }, + { "nv!a22eb0", "" }, + { "nv!a2fb451e", "" }, + { "nv!a3456abe", "" }, + { "nv!a7044887", "" }, + { "nv!a7149200", "" }, + { "nv!a766215670", "" }, + { "nv!aac_drc_boost", "" }, + { "nv!aac_drc_cut", "" }, + { "nv!aac_drc_enc_target_level", "" }, + { "nv!aac_drc_heavy", "" }, + { "nv!aac_drc_reference_level", "" }, + { "nv!aalinegamma", "" }, + { "nv!aalinetweaks", "" }, + { "nv!ab34ee01", "" }, + { "nv!ab34ee02", "" }, + { "nv!ab34ee03", "" }, + { "nv!ac0274", "" }, + { "nv!af73c63e", "" }, + { "nv!af73c63f", "" }, + { "nv!af9927", "" }, + { "nv!afoverride", "" }, + { "nv!allocdeviceevents", "" }, + { "nv!applicationkey", "" }, + { "nv!appreturnonlybasicglsltype", "" }, + { "nv!app_softimage", "" }, + { "nv!app_supportbits2", "" }, + { "nv!assumetextureismipmappedatcreation", "" }, + { "nv!b1fb0f01", "" }, + { "nv!b3edd5", "" }, + { "nv!b40d9e03d", "" }, + { "nv!b7f6275666", "" }, + { "nv!b812c1", "" }, + { "nv!ba14ba1a", "" }, + { "nv!ba14ba1b", "" }, + { "nv!bd7559", "" }, + { "nv!bd755a", "" }, + { "nv!bd755c", "" }, + { "nv!bd755d", "" }, + { "nv!be58bb", "" }, + { "nv!be92cb", "" }, + { "nv!beefcba3", "" }, + { "nv!beefcba4", "" }, + { "nv!c023777f", "" }, + { "nv!c09dc8", "" }, + { "nv!c0d340", "" }, + { "nv!c2ff374c", "" }, + { "nv!c5e9d7a3", "" }, + { "nv!c5e9d7a4", "" }, + { "nv!c5e9d7b4", "" }, + { "nv!c618f9", "" }, + { "nv!ca345840", "" }, + { "nv!cachedisable", "" }, + { "nv!cast.on", "" }, + { "nv!cde", "" }, + { "nv!channelpriorityoverride", "" }, + { "nv!cleardatastorevidmem", "" }, + { "nv!cmdbufmemoryspaceenables", "" }, + { "nv!cmdbufminwords", "" }, + { "nv!cmdbufsizewords", "" }, + { "nv!conformantblitframebufferscissor", "" }, + { "nv!conformantincompletetextures", "" }, + { "nv!copybuffermethod", "" }, + { "nv!cubemapaniso", "" }, + { "nv!cubemapfiltering", "" }, + { "nv!d0e9a4d7", "" }, + { "nv!d13733f12", "" }, + { "nv!d1b399", "" }, + { "nv!d2983c32", "" }, + { "nv!d2983c33", "" }, + { "nv!d2e71b", "" }, + { "nv!d377dc", "" }, + { "nv!d377dd", "" }, + { "nv!d489f4", "" }, + { "nv!d4bce1", "" }, + { "nv!d518cb", "" }, + { "nv!d518cd", "" }, + { "nv!d518ce", "" }, + { "nv!d518d0", "" }, + { "nv!d518d1", "" }, + { "nv!d518d2", "" }, + { "nv!d518d3", "" }, + { "nv!d518d4", "" }, + { "nv!d518d5", "" }, + { "nv!d59eda", "" }, + { "nv!d83cbd", "" }, + { "nv!d8e777", "" }, + { "nv!debug_level", "" }, + { "nv!debug_mask", "" }, + { "nv!debug_options", "" }, + { "nv!devshmpageableallocations", "" }, + { "nv!df1f9812", "" }, + { "nv!df783c", "" }, + { "nv!diagenable", "" }, + { "nv!disallowcemask", "" }, + { "nv!disallowz16", "" }, + { "nv!dlmemoryspaceenables", "" }, + { "nv!e0bfec", "" }, + { "nv!e433456d", "" }, + { "nv!e435563f", "" }, + { "nv!e4cd9c", "" }, + { "nv!e5c972", "" }, + { "nv!e639ef", "" }, + { "nv!e802af", "" }, + { "nv!eae964", "" }, + { "nv!earlytexturehwallocation", "" }, + { "nv!eb92a3", "" }, + { "nv!ebca56", "" }, + { "nv!enable-noaud", "" }, + { "nv!enable-noavs", "" }, + { "nv!enable-prof", "" }, + { "nv!enable-sxesmode", "" }, + { "nv!enable-ulld", "" }, + { "nv!expert_detail_level", "" }, + { "nv!expert_output_mask", "" }, + { "nv!expert_report_mask", "" }, + { "nv!extensionstringnvarch", "" }, + { "nv!extensionstringversion", "" }, + { "nv!f00f1938", "" }, + { "nv!f10736", "" }, + { "nv!f1846870", "" }, + { "nv!f33bc370", "" }, + { "nv!f392a874", "" }, + { "nv!f49ae8", "" }, + { "nv!fa345cce", "" }, + { "nv!fa35cc4", "" }, + { "nv!faa14a", "" }, + { "nv!faf8a723", "" }, + { "nv!fastgs", "" }, + { "nv!fbf4ac45", "" }, + { "nv!fbo_blit_ignore_srgb", "" }, + { "nv!fc64c7", "" }, + { "nv!ff54ec97", "" }, + { "nv!ff54ec98", "" }, + { "nv!forceexitprocessdetach", "" }, + { "nv!forcerequestedesversion", "" }, + { "nv!__gl_", "" }, + { "nv!__gl_00008600", "" }, + { "nv!__gl_0007b25e", "" }, + { "nv!__gl_0083e1", "" }, + { "nv!__gl_01621887", "" }, + { "nv!__gl_03134743", "" }, + { "nv!__gl_0356afd0", "" }, + { "nv!__gl_0356afd1", "" }, + { "nv!__gl_0356afd2", "" }, + { "nv!__gl_0356afd3", "" }, + { "nv!__gl_094313", "" }, + { "nv!__gl_0x04dc09", "" }, + { "nv!__gl_0x111133", "" }, + { "nv!__gl_0x1aa483", "" }, + { "nv!__gl_0x1cb1cf", "" }, + { "nv!__gl_0x1cb1d0", "" }, + { "nv!__gl_0x1e3221", "" }, + { "nv!__gl_0x300fc8", "" }, + { "nv!__gl_0x301fc8", "" }, + { "nv!__gl_0x302fc8", "" }, + { "nv!__gl_0x3eec59", "" }, + { "nv!__gl_0x46b3ed", "" }, + { "nv!__gl_0x523dc0", "" }, + { "nv!__gl_0x523dc1", "" }, + { "nv!__gl_0x523dc2", "" }, + { "nv!__gl_0x523dc3", "" }, + { "nv!__gl_0x523dc4", "" }, + { "nv!__gl_0x523dc5", "" }, + { "nv!__gl_0x523dc6", "" }, + { "nv!__gl_0x523dd0", "" }, + { "nv!__gl_0x523dd1", "" }, + { "nv!__gl_0x523dd3", "" }, + { "nv!__gl_0x5344bb", "" }, + { "nv!__gl_0x555237", "" }, + { "nv!__gl_0x58a234", "" }, + { "nv!__gl_0x7b4428", "" }, + { "nv!__gl_0x923dc0", "" }, + { "nv!__gl_0x923dc1", "" }, + { "nv!__gl_0x923dc2", "" }, + { "nv!__gl_0x923dc3", "" }, + { "nv!__gl_0x923dc4", "" }, + { "nv!__gl_0x923dd3", "" }, + { "nv!__gl_0x9abdc5", "" }, + { "nv!__gl_0x9abdc6", "" }, + { "nv!__gl_0xaaa36c", "" }, + { "nv!__gl_0xb09da0", "" }, + { "nv!__gl_0xb09da1", "" }, + { "nv!__gl_0xb09da2", "" }, + { "nv!__gl_0xb09da3", "" }, + { "nv!__gl_0xb09da4", "" }, + { "nv!__gl_0xb09da5", "" }, + { "nv!__gl_0xb0b348", "" }, + { "nv!__gl_0xb0b349", "" }, + { "nv!__gl_0xbb558f", "" }, + { "nv!__gl_0xbd10fb", "" }, + { "nv!__gl_0xc32ad3", "" }, + { "nv!__gl_0xce2348", "" }, + { "nv!__gl_0xcfd81f", "" }, + { "nv!__gl_0xe0036b", "" }, + { "nv!__gl_0xe01f2d", "" }, + { "nv!__gl_0xe17212", "" }, + { "nv!__gl_0xeae966", "" }, + { "nv!__gl_0xed4f82", "" }, + { "nv!__gl_0xf12335", "" }, + { "nv!__gl_0xf12336", "" }, + { "nv!__gl_10261989", "" }, + { "nv!__gl_1042d483", "" }, + { "nv!__gl_10572898", "" }, + { "nv!__gl_115631", "" }, + { "nv!__gl_12950094", "" }, + { "nv!__gl_1314f311", "" }, + { "nv!__gl_1314f312", "" }, + { "nv!__gl_13279512", "" }, + { "nv!__gl_13813496", "" }, + { "nv!__gl_14507179", "" }, + { "nv!__gl_15694569", "" }, + { "nv!__gl_16936964", "" }, + { "nv!__gl_17aa230c", "" }, + { "nv!__gl_182054", "" }, + { "nv!__gl_18273275", "" }, + { "nv!__gl_18273276", "" }, + { "nv!__gl_1854d03b", "" }, + { "nv!__gl_18add00d", "" }, + { "nv!__gl_19156670", "" }, + { "nv!__gl_19286545", "" }, + { "nv!__gl_1a298e9f", "" }, + { "nv!__gl_1acf43fe", "" }, + { "nv!__gl_1bda43fe", "" }, + { "nv!__gl_1c3b92", "" }, + { "nv!__gl_21509920", "" }, + { "nv!__gl_215323457", "" }, + { "nv!__gl_2165ad", "" }, + { "nv!__gl_2165ae", "" }, + { "nv!__gl_21be9c", "" }, + { "nv!__gl_233264316", "" }, + { "nv!__gl_234557580", "" }, + { "nv!__gl_23cd0e", "" }, + { "nv!__gl_24189123", "" }, + { "nv!__gl_2443266", "" }, + { "nv!__gl_25025519", "" }, + { "nv!__gl_255e39", "" }, + { "nv!__gl_2583364", "" }, + { "nv!__gl_2888c1", "" }, + { "nv!__gl_28ca3e", "" }, + { "nv!__gl_29871243", "" }, + { "nv!__gl_2a1f64", "" }, + { "nv!__gl_2dc432", "" }, + { "nv!__gl_2de437", "" }, + { "nv!__gl_2f3bb89c", "" }, + { "nv!__gl_2fd652", "" }, + { "nv!__gl_3001ac", "" }, + { "nv!__gl_31298772", "" }, + { "nv!__gl_313233", "" }, + { "nv!__gl_31f7d603", "" }, + { "nv!__gl_320ce4", "" }, + { "nv!__gl_32153248", "" }, + { "nv!__gl_32153249", "" }, + { "nv!__gl_335bca", "" }, + { "nv!__gl_342abb", "" }, + { "nv!__gl_34dfe6", "" }, + { "nv!__gl_34dfe7", "" }, + { "nv!__gl_34dfe8", "" }, + { "nv!__gl_34dfe9", "" }, + { "nv!__gl_35201578", "" }, + { "nv!__gl_359278", "" }, + { "nv!__gl_37f53a", "" }, + { "nv!__gl_38144972", "" }, + { "nv!__gl_38542646", "" }, + { "nv!__gl_3b74c9", "" }, + { "nv!__gl_3c136f", "" }, + { "nv!__gl_3cf72823", "" }, + { "nv!__gl_3d7af029", "" }, + { "nv!__gl_3ff34782", "" }, + { "nv!__gl_4129618", "" }, + { "nv!__gl_4189fac3", "" }, + { "nv!__gl_420bd4", "" }, + { "nv!__gl_42a699", "" }, + { "nv!__gl_441369", "" }, + { "nv!__gl_4458713e", "" }, + { "nv!__gl_4554b6", "" }, + { "nv!__gl_457425", "" }, + { "nv!__gl_4603b207", "" }, + { "nv!__gl_46574957", "" }, + { "nv!__gl_46574958", "" }, + { "nv!__gl_46813529", "" }, + { "nv!__gl_46f1e13d", "" }, + { "nv!__gl_47534c43", "" }, + { "nv!__gl_48550336", "" }, + { "nv!__gl_48576893", "" }, + { "nv!__gl_48576894", "" }, + { "nv!__gl_4889ac02", "" }, + { "nv!__gl_49005740", "" }, + { "nv!__gl_49867584", "" }, + { "nv!__gl_49960973", "" }, + { "nv!__gl_4a5341", "" }, + { "nv!__gl_4f4e48", "" }, + { "nv!__gl_4f8a0a", "" }, + { "nv!__gl_50299698", "" }, + { "nv!__gl_50299699", "" }, + { "nv!__gl_50361291", "" }, + { "nv!__gl_5242ae", "" }, + { "nv!__gl_53d30c", "" }, + { "nv!__gl_56347a", "" }, + { "nv!__gl_563a95f1", "" }, + { "nv!__gl_573823", "" }, + { "nv!__gl_58027529", "" }, + { "nv!__gl_5d2d63", "" }, + { "nv!__gl_5f7e3b", "" }, + { "nv!__gl_60461793", "" }, + { "nv!__gl_60d355", "" }, + { "nv!__gl_616627aa", "" }, + { "nv!__gl_62317182", "" }, + { "nv!__gl_6253fa2e", "" }, + { "nv!__gl_64100768", "" }, + { "nv!__gl_64100769", "" }, + { "nv!__gl_64100770", "" }, + { "nv!__gl_647395", "" }, + { "nv!__gl_66543234", "" }, + { "nv!__gl_67674763", "" }, + { "nv!__gl_67739784", "" }, + { "nv!__gl_68fb9c", "" }, + { "nv!__gl_69801276", "" }, + { "nv!__gl_6af9fa2f", "" }, + { "nv!__gl_6af9fa3f", "" }, + { "nv!__gl_6af9fa4f", "" }, + { "nv!__gl_6bd8c7", "" }, + { "nv!__gl_6c7691", "" }, + { "nv!__gl_6d4296ce", "" }, + { "nv!__gl_6dd7e7", "" }, + { "nv!__gl_6dd7e8", "" }, + { "nv!__gl_6fe11ec1", "" }, + { "nv!__gl_716511763", "" }, + { "nv!__gl_72504593", "" }, + { "nv!__gl_73304097", "" }, + { "nv!__gl_73314098", "" }, + { "nv!__gl_74095213", "" }, + { "nv!__gl_74095213a", "" }, + { "nv!__gl_74095213b", "" }, + { "nv!__gl_74095214", "" }, + { "nv!__gl_748f9649", "" }, + { "nv!__gl_75494732", "" }, + { "nv!__gl_78452832", "" }, + { "nv!__gl_784561", "" }, + { "nv!__gl_78e16b9c", "" }, + { "nv!__gl_79251225", "" }, + { "nv!__gl_7c128b", "" }, + { "nv!__gl_7ccd93", "" }, + { "nv!__gl_7df8d1", "" }, + { "nv!__gl_800c2310", "" }, + { "nv!__gl_80546710", "" }, + { "nv!__gl_80772310", "" }, + { "nv!__gl_808ee280", "" }, + { "nv!__gl_81131154", "" }, + { "nv!__gl_81274457", "" }, + { "nv!__gl_8292291f", "" }, + { "nv!__gl_83498426", "" }, + { "nv!__gl_84993794", "" }, + { "nv!__gl_84995585", "" }, + { "nv!__gl_84a0a0", "" }, + { "nv!__gl_852142", "" }, + { "nv!__gl_85612309", "" }, + { "nv!__gl_85612310", "" }, + { "nv!__gl_85612311", "" }, + { "nv!__gl_85612312", "" }, + { "nv!__gl_8623ff27", "" }, + { "nv!__gl_87364952", "" }, + { "nv!__gl_87f6275666", "" }, + { "nv!__gl_886748", "" }, + { "nv!__gl_89894423", "" }, + { "nv!__gl_8ad8a75", "" }, + { "nv!__gl_8ad8ad00", "" }, + { "nv!__gl_8bb815", "" }, + { "nv!__gl_8bb817", "" }, + { "nv!__gl_8bb818", "" }, + { "nv!__gl_8bb819", "" }, + { "nv!__gl_8e640cd1", "" }, + { "nv!__gl_8f34971a", "" }, + { "nv!__gl_8f773984", "" }, + { "nv!__gl_8f7a7d", "" }, + { "nv!__gl_902486209", "" }, + { "nv!__gl_90482571", "" }, + { "nv!__gl_91214835", "" }, + { "nv!__gl_912848290", "" }, + { "nv!__gl_915e56", "" }, + { "nv!__gl_92179063", "" }, + { "nv!__gl_92179064", "" }, + { "nv!__gl_92179065", "" }, + { "nv!__gl_92179066", "" }, + { "nv!__gl_92350358", "" }, + { "nv!__gl_92809063", "" }, + { "nv!__gl_92809064", "" }, + { "nv!__gl_92809065", "" }, + { "nv!__gl_92809066", "" }, + { "nv!__gl_92920143", "" }, + { "nv!__gl_93a89b12", "" }, + { "nv!__gl_93a89c0b", "" }, + { "nv!__gl_94812574", "" }, + { "nv!__gl_95282304", "" }, + { "nv!__gl_95394027", "" }, + { "nv!__gl_959b1f", "" }, + { "nv!__gl_9638af", "" }, + { "nv!__gl_96fd59", "" }, + { "nv!__gl_97f6275666", "" }, + { "nv!__gl_97f6275667", "" }, + { "nv!__gl_97f6275668", "" }, + { "nv!__gl_97f6275669", "" }, + { "nv!__gl_97f627566a", "" }, + { "nv!__gl_97f627566b", "" }, + { "nv!__gl_97f627566d", "" }, + { "nv!__gl_97f627566e", "" }, + { "nv!__gl_97f627566f", "" }, + { "nv!__gl_97f6275670", "" }, + { "nv!__gl_97f6275671", "" }, + { "nv!__gl_97f727566e", "" }, + { "nv!__gl_98480775", "" }, + { "nv!__gl_98480776", "" }, + { "nv!__gl_98480777", "" }, + { "nv!__gl_992431", "" }, + { "nv!__gl_9aa29065", "" }, + { "nv!__gl_9af32c", "" }, + { "nv!__gl_9af32d", "" }, + { "nv!__gl_9af32e", "" }, + { "nv!__gl_9c108b71", "" }, + { "nv!__gl_9f279065", "" }, + { "nv!__gl_a01bc728", "" }, + { "nv!__gl_a13b46c80", "" }, + { "nv!__gl_a22eb0", "" }, + { "nv!__gl_a2fb451e", "" }, + { "nv!__gl_a3456abe", "" }, + { "nv!__gl_a7044887", "" }, + { "nv!__gl_a7149200", "" }, + { "nv!__gl_a766215670", "" }, + { "nv!__gl_aalinegamma", "" }, + { "nv!__gl_aalinetweaks", "" }, + { "nv!__gl_ab34ee01", "" }, + { "nv!__gl_ab34ee02", "" }, + { "nv!__gl_ab34ee03", "" }, + { "nv!__gl_ac0274", "" }, + { "nv!__gl_af73c63e", "" }, + { "nv!__gl_af73c63f", "" }, + { "nv!__gl_af9927", "" }, + { "nv!__gl_afoverride", "" }, + { "nv!__gl_allocdeviceevents", "" }, + { "nv!__gl_applicationkey", "" }, + { "nv!__gl_appreturnonlybasicglsltype", "" }, + { "nv!__gl_app_softimage", "" }, + { "nv!__gl_app_supportbits2", "" }, + { "nv!__gl_assumetextureismipmappedatcreation", "" }, + { "nv!__gl_b1fb0f01", "" }, + { "nv!__gl_b3edd5", "" }, + { "nv!__gl_b40d9e03d", "" }, + { "nv!__gl_b7f6275666", "" }, + { "nv!__gl_b812c1", "" }, + { "nv!__gl_ba14ba1a", "" }, + { "nv!__gl_ba14ba1b", "" }, + { "nv!__gl_bd7559", "" }, + { "nv!__gl_bd755a", "" }, + { "nv!__gl_bd755c", "" }, + { "nv!__gl_bd755d", "" }, + { "nv!__gl_be58bb", "" }, + { "nv!__gl_be92cb", "" }, + { "nv!__gl_beefcba3", "" }, + { "nv!__gl_beefcba4", "" }, + { "nv!__gl_c023777f", "" }, + { "nv!__gl_c09dc8", "" }, + { "nv!__gl_c0d340", "" }, + { "nv!__gl_c2ff374c", "" }, + { "nv!__gl_c5e9d7a3", "" }, + { "nv!__gl_c5e9d7a4", "" }, + { "nv!__gl_c5e9d7b4", "" }, + { "nv!__gl_c618f9", "" }, + { "nv!__gl_ca345840", "" }, + { "nv!__gl_cachedisable", "" }, + { "nv!__gl_channelpriorityoverride", "" }, + { "nv!__gl_cleardatastorevidmem", "" }, + { "nv!__gl_cmdbufmemoryspaceenables", "" }, + { "nv!__gl_cmdbufminwords", "" }, + { "nv!__gl_cmdbufsizewords", "" }, + { "nv!__gl_conformantblitframebufferscissor", "" }, + { "nv!__gl_conformantincompletetextures", "" }, + { "nv!__gl_copybuffermethod", "" }, + { "nv!__gl_cubemapaniso", "" }, + { "nv!__gl_cubemapfiltering", "" }, + { "nv!__gl_d0e9a4d7", "" }, + { "nv!__gl_d13733f12", "" }, + { "nv!__gl_d1b399", "" }, + { "nv!__gl_d2983c32", "" }, + { "nv!__gl_d2983c33", "" }, + { "nv!__gl_d2e71b", "" }, + { "nv!__gl_d377dc", "" }, + { "nv!__gl_d377dd", "" }, + { "nv!__gl_d489f4", "" }, + { "nv!__gl_d4bce1", "" }, + { "nv!__gl_d518cb", "" }, + { "nv!__gl_d518cd", "" }, + { "nv!__gl_d518ce", "" }, + { "nv!__gl_d518d0", "" }, + { "nv!__gl_d518d1", "" }, + { "nv!__gl_d518d2", "" }, + { "nv!__gl_d518d3", "" }, + { "nv!__gl_d518d4", "" }, + { "nv!__gl_d518d5", "" }, + { "nv!__gl_d59eda", "" }, + { "nv!__gl_d83cbd", "" }, + { "nv!__gl_d8e777", "" }, + { "nv!__gl_debug_level", "" }, + { "nv!__gl_debug_mask", "" }, + { "nv!__gl_debug_options", "" }, + { "nv!__gl_devshmpageableallocations", "" }, + { "nv!__gl_df1f9812", "" }, + { "nv!__gl_df783c", "" }, + { "nv!__gl_diagenable", "" }, + { "nv!__gl_disallowcemask", "" }, + { "nv!__gl_disallowz16", "" }, + { "nv!__gl_dlmemoryspaceenables", "" }, + { "nv!__gl_e0bfec", "" }, + { "nv!__gl_e433456d", "" }, + { "nv!__gl_e435563f", "" }, + { "nv!__gl_e4cd9c", "" }, + { "nv!__gl_e5c972", "" }, + { "nv!__gl_e639ef", "" }, + { "nv!__gl_e802af", "" }, + { "nv!__gl_eae964", "" }, + { "nv!__gl_earlytexturehwallocation", "" }, + { "nv!__gl_eb92a3", "" }, + { "nv!__gl_ebca56", "" }, + { "nv!__gl_expert_detail_level", "" }, + { "nv!__gl_expert_output_mask", "" }, + { "nv!__gl_expert_report_mask", "" }, + { "nv!__gl_extensionstringnvarch", "" }, + { "nv!__gl_extensionstringversion", "" }, + { "nv!__gl_f00f1938", "" }, + { "nv!__gl_f10736", "" }, + { "nv!__gl_f1846870", "" }, + { "nv!__gl_f33bc370", "" }, + { "nv!__gl_f392a874", "" }, + { "nv!__gl_f49ae8", "" }, + { "nv!__gl_fa345cce", "" }, + { "nv!__gl_fa35cc4", "" }, + { "nv!__gl_faa14a", "" }, + { "nv!__gl_faf8a723", "" }, + { "nv!__gl_fastgs", "" }, + { "nv!__gl_fbf4ac45", "" }, + { "nv!__gl_fbo_blit_ignore_srgb", "" }, + { "nv!__gl_fc64c7", "" }, + { "nv!__gl_ff54ec97", "" }, + { "nv!__gl_ff54ec98", "" }, + { "nv!__gl_forceexitprocessdetach", "" }, + { "nv!__gl_forcerequestedesversion", "" }, + { "nv!__gl_glsynctovblank", "" }, + { "nv!__gl_gvitimeoutcontrol", "" }, + { "nv!__gl_hcctrl", "" }, + { "nv!__gl_hwstate_per_ctx", "" }, + { "nv!__gl_machinecachelimit", "" }, + { "nv!__gl_maxframesallowed", "" }, + { "nv!__gl_memmgrcachedalloclimit", "" }, + { "nv!__gl_memmgrcachedalloclimitratio", "" }, + { "nv!__gl_memmgrsysheapalloclimit", "" }, + { "nv!__gl_memmgrsysheapalloclimitratio", "" }, + { "nv!__gl_memmgrvidheapalloclimit", "" }, + { "nv!__gl_mosaic_clip_to_subdev", "" }, + { "nv!__gl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!__gl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!__gl_overlaymergeblittimerms", "" }, + { "nv!__gl_perfmon_mode", "" }, + { "nv!__gl_pixbar_mode", "" }, + { "nv!__gl_qualityenhancements", "" }, + { "nv!__gl_r27s18q28", "" }, + { "nv!__gl_r2d7c1d8", "" }, + { "nv!__gl_renderer", "" }, + { "nv!__gl_renderqualityflags", "" }, + { "nv!__gl_s3tcquality", "" }, + { "nv!__gl_shaderatomics", "" }, + { "nv!__gl_shadercacheinitsize", "" }, + { "nv!__gl_shader_disk_cache_path", "" }, + { "nv!__gl_shader_disk_cache_read_only", "" }, + { "nv!__gl_shaderobjects", "" }, + { "nv!__gl_shaderportabilitywarnings", "" }, + { "nv!__gl_shaderwarningsaserrors", "" }, + { "nv!__gl_skiptexturehostcopies", "" }, + { "nv!__glslc_debug_level", "" }, + { "nv!__glslc_debug_mask", "" }, + { "nv!__glslc_debug_options", "" }, + { "nv!__glslc_debug_filename", "" }, + { "nv!__gl_sli_dli_control", "" }, + { "nv!__gl_sparsetexture", "" }, + { "nv!__gl_spinlooptimeout", "" }, + { "nv!__gl_sync_to_vblank", "" }, + { "nv!glsynctovblank", "" }, + { "nv!__gl_sysheapreuseratio", "" }, + { "nv!__gl_sysmemtexturepromotion", "" }, + { "nv!__gl_targetflushcount", "" }, + { "nv!__gl_tearingfreeswappresent", "" }, + { "nv!__gl_texclampbehavior", "" }, + { "nv!__gl_texlodbias", "" }, + { "nv!__gl_texmemoryspaceenables", "" }, + { "nv!__gl_textureprecache", "" }, + { "nv!__gl_threadcontrol", "" }, + { "nv!__gl_threadcontrol2", "" }, + { "nv!__gl_usegvievents", "" }, + { "nv!__gl_vbomemoryspaceenables", "" }, + { "nv!__gl_vertexlimit", "" }, + { "nv!__gl_vidheapreuseratio", "" }, + { "nv!__gl_vpipe", "" }, + { "nv!__gl_vpipeformatbloatlimit", "" }, + { "nv!__gl_wglmessageboxonabort", "" }, + { "nv!__gl_writeinfolog", "" }, + { "nv!__gl_writeprogramobjectassembly", "" }, + { "nv!__gl_writeprogramobjectsource", "" }, + { "nv!__gl_xnvadapterpresent", "" }, + { "nv!__gl_yield", "" }, + { "nv!__gl_yieldfunction", "" }, + { "nv!__gl_yieldfunctionfast", "" }, + { "nv!__gl_yieldfunctionslow", "" }, + { "nv!__gl_yieldfunctionwaitfordcqueue", "" }, + { "nv!__gl_yieldfunctionwaitforframe", "" }, + { "nv!__gl_yieldfunctionwaitforgpu", "" }, + { "nv!__gl_zbctableaddhysteresis", "" }, + { "nv!gpu_debug_mode", "" }, + { "nv!gpu_stay_on", "" }, + { "nv!gpu_timeout_ms_max", "" }, + { "nv!gvitimeoutcontrol", "" }, + { "nv!hcctrl", "" }, + { "nv!hwstate_per_ctx", "" }, + { "nv!libandroid_enable_log", "" }, + { "nv!machinecachelimit", "" }, + { "nv!maxframesallowed", "" }, + { "nv!media.aac_51_output_enabled", "" }, + { "nv!memmgrcachedalloclimit", "" }, + { "nv!memmgrcachedalloclimitratio", "" }, + { "nv!memmgrsysheapalloclimit", "" }, + { "nv!memmgrsysheapalloclimitratio", "" }, + { "nv!memmgrvidheapalloclimit", "" }, + { "nv!mosaic_clip_to_subdev", "" }, + { "nv!mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!nvblit.dump", "" }, + { "nv!nvblit.profile", "" }, + { "nv!nvblit.twod", "" }, + { "nv!nvblit.vic", "" }, + { "nv!nvddk_vic_prevent_use", "" }, + { "nv!nv_decompression", "" }, + { "nv!nvdisp_bl_ctrl", "0" }, + { "nv!nvdisp_debug_mask", "" }, + { "nv!nvdisp_enable_ts", "0" }, + { "nv!nvhdcp_timeout_ms", "12000" }, + { "nv!nvhdcp_max_retries", "5" }, + { "nv!nv_emc_dvfs_test", "" }, + { "nv!nv_emc_init_rate_hz", "" }, + { "nv!nv_gmmu_va_page_split", "" }, + { "nv!nv_gmmu_va_range", "" }, + { "nv!nvhost_debug_mask", "" }, + { "nv!nvidia.hwc.dump_config", "" }, + { "nv!nvidia.hwc.dump_layerlist", "" }, + { "nv!nvidia.hwc.dump_windows", "" }, + { "nv!nvidia.hwc.enable_disp_trans", "" }, + { "nv!nvidia.hwc.ftrace_enable", "" }, + { "nv!nvidia.hwc.hdcp_enable", "" }, + { "nv!nvidia.hwc.hidden_window_mask0", "" }, + { "nv!nvidia.hwc.hidden_window_mask1", "" }, + { "nv!nvidia.hwc.immediate_modeset", "" }, + { "nv!nvidia.hwc.imp_enable", "" }, + { "nv!nvidia.hwc.no_egl", "" }, + { "nv!nvidia.hwc.no_scratchblit", "" }, + { "nv!nvidia.hwc.no_vic", "" }, + { "nv!nvidia.hwc.null_display", "" }, + { "nv!nvidia.hwc.scan_props", "" }, + { "nv!nvidia.hwc.swap_interval", "" }, + { "nv!nvidia.hwc.war_1515812", "0" }, + { "nv!nvmap_debug_mask", "" }, + { "nv!nv_memory_profiler", "" }, + { "nv!nvnflinger_enable_log", "" }, + { "nv!nvnflinger_flip_policy", "" }, + { "nv!nvnflinger_hotplug_autoswitch", "0" }, + { "nv!nvnflinger_prefer_primary_layer", "0" }, + { "nv!nvnflinger_service_priority", "" }, + { "nv!nvnflinger_service_threads", "" }, + { "nv!nvnflinger_swap_interval", "" }, + { "nv!nvnflinger_track_perf", "" }, + { "nv!nvnflinger_virtualdisplay_policy", "60hz" }, + { "nv!nvn_no_vsync_capability", false }, + { "nv!nvn_through_opengl", "" }, + { "nv!nv_pllcx_always_on", "" }, + { "nv!nv_pllcx_safe_div", "" }, + { "nv!nvrm_gpu_channel_interleave", "" }, + { "nv!nvrm_gpu_channel_priority", "" }, + { "nv!nvrm_gpu_channel_timeslice", "" }, + { "nv!nvrm_gpu_default_device_index", "" }, + { "nv!nvrm_gpu_dummy", "" }, + { "nv!nvrm_gpu_help", "" }, + { "nv!nvrm_gpu_nvgpu_disable", "" }, + { "nv!nvrm_gpu_nvgpu_do_nfa_partial_map", "" }, + { "nv!nvrm_gpu_nvgpu_ecc_overrides", "" }, + { "nv!nvrm_gpu_nvgpu_no_as_get_va_regions", "" }, + { "nv!nvrm_gpu_nvgpu_no_channel_abort", "" }, + { "nv!nvrm_gpu_nvgpu_no_cyclestats", "" }, + { "nv!nvrm_gpu_nvgpu_no_fixed", "" }, + { "nv!nvrm_gpu_nvgpu_no_gpu_characteristics", "" }, + { "nv!nvrm_gpu_nvgpu_no_ioctl_mutex", "" }, + { "nv!nvrm_gpu_nvgpu_no_map_buffer_ex", "" }, + { "nv!nvrm_gpu_nvgpu_no_robustness", "" }, + { "nv!nvrm_gpu_nvgpu_no_sparse", "" }, + { "nv!nvrm_gpu_nvgpu_no_syncpoints", "" }, + { "nv!nvrm_gpu_nvgpu_no_tsg", "" }, + { "nv!nvrm_gpu_nvgpu_no_zbc", "" }, + { "nv!nvrm_gpu_nvgpu_no_zcull", "" }, + { "nv!nvrm_gpu_nvgpu_wrap_channels_in_tsgs", "" }, + { "nv!nvrm_gpu_prevent_use", "" }, + { "nv!nvrm_gpu_trace", "" }, + { "nv!nvsched_debug_mask", "" }, + { "nv!nvsched_force_enable", "" }, + { "nv!nvsched_force_log", "" }, + { "nv!nv_usb_plls_hw_ctrl", "" }, + { "nv!nv_winsys", "" }, + { "nv!nvwsi_dump", "" }, + { "nv!nvwsi_fill", "" }, + { "nv!ogl_", "" }, + { "nv!ogl_0356afd0", "" }, + { "nv!ogl_0356afd1", "" }, + { "nv!ogl_0356afd2", "" }, + { "nv!ogl_0356afd3", "" }, + { "nv!ogl_0x923dc0", "" }, + { "nv!ogl_0x923dc1", "" }, + { "nv!ogl_0x923dc2", "" }, + { "nv!ogl_0x923dc3", "" }, + { "nv!ogl_0x923dc4", "" }, + { "nv!ogl_0x923dd3", "" }, + { "nv!ogl_0x9abdc5", "" }, + { "nv!ogl_0x9abdc6", "" }, + { "nv!ogl_0xbd10fb", "" }, + { "nv!ogl_0xce2348", "" }, + { "nv!ogl_10261989", "" }, + { "nv!ogl_1042d483", "" }, + { "nv!ogl_10572898", "" }, + { "nv!ogl_115631", "" }, + { "nv!ogl_12950094", "" }, + { "nv!ogl_1314f311", "" }, + { "nv!ogl_1314f312", "" }, + { "nv!ogl_13279512", "" }, + { "nv!ogl_13813496", "" }, + { "nv!ogl_14507179", "" }, + { "nv!ogl_15694569", "" }, + { "nv!ogl_16936964", "" }, + { "nv!ogl_17aa230c", "" }, + { "nv!ogl_182054", "" }, + { "nv!ogl_18273275", "" }, + { "nv!ogl_18273276", "" }, + { "nv!ogl_1854d03b", "" }, + { "nv!ogl_18add00d", "" }, + { "nv!ogl_19156670", "" }, + { "nv!ogl_19286545", "" }, + { "nv!ogl_1a298e9f", "" }, + { "nv!ogl_1acf43fe", "" }, + { "nv!ogl_1bda43fe", "" }, + { "nv!ogl_1c3b92", "" }, + { "nv!ogl_21509920", "" }, + { "nv!ogl_215323457", "" }, + { "nv!ogl_2165ad", "" }, + { "nv!ogl_2165ae", "" }, + { "nv!ogl_21be9c", "" }, + { "nv!ogl_233264316", "" }, + { "nv!ogl_234557580", "" }, + { "nv!ogl_23cd0e", "" }, + { "nv!ogl_24189123", "" }, + { "nv!ogl_2443266", "" }, + { "nv!ogl_25025519", "" }, + { "nv!ogl_255e39", "" }, + { "nv!ogl_2583364", "" }, + { "nv!ogl_2888c1", "" }, + { "nv!ogl_28ca3e", "" }, + { "nv!ogl_29871243", "" }, + { "nv!ogl_2a1f64", "" }, + { "nv!ogl_2dc432", "" }, + { "nv!ogl_2de437", "" }, + { "nv!ogl_2f3bb89c", "" }, + { "nv!ogl_2fd652", "" }, + { "nv!ogl_3001ac", "" }, + { "nv!ogl_31298772", "" }, + { "nv!ogl_313233", "" }, + { "nv!ogl_31f7d603", "" }, + { "nv!ogl_320ce4", "" }, + { "nv!ogl_32153248", "" }, + { "nv!ogl_32153249", "" }, + { "nv!ogl_335bca", "" }, + { "nv!ogl_342abb", "" }, + { "nv!ogl_34dfe6", "" }, + { "nv!ogl_34dfe7", "" }, + { "nv!ogl_34dfe8", "" }, + { "nv!ogl_34dfe9", "" }, + { "nv!ogl_35201578", "" }, + { "nv!ogl_359278", "" }, + { "nv!ogl_37f53a", "" }, + { "nv!ogl_38144972", "" }, + { "nv!ogl_38542646", "" }, + { "nv!ogl_3b74c9", "" }, + { "nv!ogl_3c136f", "" }, + { "nv!ogl_3cf72823", "" }, + { "nv!ogl_3d7af029", "" }, + { "nv!ogl_3ff34782", "" }, + { "nv!ogl_4129618", "" }, + { "nv!ogl_4189fac3", "" }, + { "nv!ogl_420bd4", "" }, + { "nv!ogl_42a699", "" }, + { "nv!ogl_441369", "" }, + { "nv!ogl_4458713e", "" }, + { "nv!ogl_4554b6", "" }, + { "nv!ogl_457425", "" }, + { "nv!ogl_4603b207", "" }, + { "nv!ogl_46574957", "" }, + { "nv!ogl_46574958", "" }, + { "nv!ogl_46813529", "" }, + { "nv!ogl_46f1e13d", "" }, + { "nv!ogl_47534c43", "" }, + { "nv!ogl_48550336", "" }, + { "nv!ogl_48576893", "" }, + { "nv!ogl_48576894", "" }, + { "nv!ogl_4889ac02", "" }, + { "nv!ogl_49005740", "" }, + { "nv!ogl_49867584", "" }, + { "nv!ogl_49960973", "" }, + { "nv!ogl_4a5341", "" }, + { "nv!ogl_4f4e48", "" }, + { "nv!ogl_4f8a0a", "" }, + { "nv!ogl_50299698", "" }, + { "nv!ogl_50299699", "" }, + { "nv!ogl_50361291", "" }, + { "nv!ogl_5242ae", "" }, + { "nv!ogl_53d30c", "" }, + { "nv!ogl_56347a", "" }, + { "nv!ogl_563a95f1", "" }, + { "nv!ogl_573823", "" }, + { "nv!ogl_58027529", "" }, + { "nv!ogl_5d2d63", "" }, + { "nv!ogl_5f7e3b", "" }, + { "nv!ogl_60461793", "" }, + { "nv!ogl_60d355", "" }, + { "nv!ogl_616627aa", "" }, + { "nv!ogl_62317182", "" }, + { "nv!ogl_6253fa2e", "" }, + { "nv!ogl_64100768", "" }, + { "nv!ogl_64100769", "" }, + { "nv!ogl_64100770", "" }, + { "nv!ogl_647395", "" }, + { "nv!ogl_66543234", "" }, + { "nv!ogl_67674763", "" }, + { "nv!ogl_67739784", "" }, + { "nv!ogl_68fb9c", "" }, + { "nv!ogl_69801276", "" }, + { "nv!ogl_6af9fa2f", "" }, + { "nv!ogl_6af9fa3f", "" }, + { "nv!ogl_6af9fa4f", "" }, + { "nv!ogl_6bd8c7", "" }, + { "nv!ogl_6c7691", "" }, + { "nv!ogl_6d4296ce", "" }, + { "nv!ogl_6dd7e7", "" }, + { "nv!ogl_6dd7e8", "" }, + { "nv!ogl_6fe11ec1", "" }, + { "nv!ogl_716511763", "" }, + { "nv!ogl_72504593", "" }, + { "nv!ogl_73304097", "" }, + { "nv!ogl_73314098", "" }, + { "nv!ogl_74095213", "" }, + { "nv!ogl_74095213a", "" }, + { "nv!ogl_74095213b", "" }, + { "nv!ogl_74095214", "" }, + { "nv!ogl_748f9649", "" }, + { "nv!ogl_75494732", "" }, + { "nv!ogl_78452832", "" }, + { "nv!ogl_784561", "" }, + { "nv!ogl_78e16b9c", "" }, + { "nv!ogl_79251225", "" }, + { "nv!ogl_7c128b", "" }, + { "nv!ogl_7ccd93", "" }, + { "nv!ogl_7df8d1", "" }, + { "nv!ogl_800c2310", "" }, + { "nv!ogl_80546710", "" }, + { "nv!ogl_80772310", "" }, + { "nv!ogl_808ee280", "" }, + { "nv!ogl_81131154", "" }, + { "nv!ogl_81274457", "" }, + { "nv!ogl_8292291f", "" }, + { "nv!ogl_83498426", "" }, + { "nv!ogl_84993794", "" }, + { "nv!ogl_84995585", "" }, + { "nv!ogl_84a0a0", "" }, + { "nv!ogl_852142", "" }, + { "nv!ogl_85612309", "" }, + { "nv!ogl_85612310", "" }, + { "nv!ogl_85612311", "" }, + { "nv!ogl_85612312", "" }, + { "nv!ogl_8623ff27", "" }, + { "nv!ogl_87364952", "" }, + { "nv!ogl_87f6275666", "" }, + { "nv!ogl_886748", "" }, + { "nv!ogl_89894423", "" }, + { "nv!ogl_8ad8a75", "" }, + { "nv!ogl_8ad8ad00", "" }, + { "nv!ogl_8bb815", "" }, + { "nv!ogl_8bb817", "" }, + { "nv!ogl_8bb818", "" }, + { "nv!ogl_8bb819", "" }, + { "nv!ogl_8e640cd1", "" }, + { "nv!ogl_8f34971a", "" }, + { "nv!ogl_8f773984", "" }, + { "nv!ogl_8f7a7d", "" }, + { "nv!ogl_902486209", "" }, + { "nv!ogl_90482571", "" }, + { "nv!ogl_91214835", "" }, + { "nv!ogl_912848290", "" }, + { "nv!ogl_915e56", "" }, + { "nv!ogl_92179063", "" }, + { "nv!ogl_92179064", "" }, + { "nv!ogl_92179065", "" }, + { "nv!ogl_92179066", "" }, + { "nv!ogl_92350358", "" }, + { "nv!ogl_92809063", "" }, + { "nv!ogl_92809064", "" }, + { "nv!ogl_92809065", "" }, + { "nv!ogl_92809066", "" }, + { "nv!ogl_92920143", "" }, + { "nv!ogl_93a89b12", "" }, + { "nv!ogl_93a89c0b", "" }, + { "nv!ogl_94812574", "" }, + { "nv!ogl_95282304", "" }, + { "nv!ogl_95394027", "" }, + { "nv!ogl_959b1f", "" }, + { "nv!ogl_9638af", "" }, + { "nv!ogl_96fd59", "" }, + { "nv!ogl_97f6275666", "" }, + { "nv!ogl_97f6275667", "" }, + { "nv!ogl_97f6275668", "" }, + { "nv!ogl_97f6275669", "" }, + { "nv!ogl_97f627566a", "" }, + { "nv!ogl_97f627566b", "" }, + { "nv!ogl_97f627566d", "" }, + { "nv!ogl_97f627566e", "" }, + { "nv!ogl_97f627566f", "" }, + { "nv!ogl_97f6275670", "" }, + { "nv!ogl_97f6275671", "" }, + { "nv!ogl_97f727566e", "" }, + { "nv!ogl_98480775", "" }, + { "nv!ogl_98480776", "" }, + { "nv!ogl_98480777", "" }, + { "nv!ogl_992431", "" }, + { "nv!ogl_9aa29065", "" }, + { "nv!ogl_9af32c", "" }, + { "nv!ogl_9af32d", "" }, + { "nv!ogl_9af32e", "" }, + { "nv!ogl_9c108b71", "" }, + { "nv!ogl_9f279065", "" }, + { "nv!ogl_a01bc728", "" }, + { "nv!ogl_a13b46c80", "" }, + { "nv!ogl_a22eb0", "" }, + { "nv!ogl_a2fb451e", "" }, + { "nv!ogl_a3456abe", "" }, + { "nv!ogl_a7044887", "" }, + { "nv!ogl_a7149200", "" }, + { "nv!ogl_a766215670", "" }, + { "nv!ogl_aalinegamma", "" }, + { "nv!ogl_aalinetweaks", "" }, + { "nv!ogl_ab34ee01", "" }, + { "nv!ogl_ab34ee02", "" }, + { "nv!ogl_ab34ee03", "" }, + { "nv!ogl_ac0274", "" }, + { "nv!ogl_af73c63e", "" }, + { "nv!ogl_af73c63f", "" }, + { "nv!ogl_af9927", "" }, + { "nv!ogl_afoverride", "" }, + { "nv!ogl_allocdeviceevents", "" }, + { "nv!ogl_applicationkey", "" }, + { "nv!ogl_appreturnonlybasicglsltype", "" }, + { "nv!ogl_app_softimage", "" }, + { "nv!ogl_app_supportbits2", "" }, + { "nv!ogl_assumetextureismipmappedatcreation", "" }, + { "nv!ogl_b1fb0f01", "" }, + { "nv!ogl_b3edd5", "" }, + { "nv!ogl_b40d9e03d", "" }, + { "nv!ogl_b7f6275666", "" }, + { "nv!ogl_b812c1", "" }, + { "nv!ogl_ba14ba1a", "" }, + { "nv!ogl_ba14ba1b", "" }, + { "nv!ogl_bd7559", "" }, + { "nv!ogl_bd755a", "" }, + { "nv!ogl_bd755c", "" }, + { "nv!ogl_bd755d", "" }, + { "nv!ogl_be58bb", "" }, + { "nv!ogl_be92cb", "" }, + { "nv!ogl_beefcba3", "" }, + { "nv!ogl_beefcba4", "" }, + { "nv!ogl_c023777f", "" }, + { "nv!ogl_c09dc8", "" }, + { "nv!ogl_c0d340", "" }, + { "nv!ogl_c2ff374c", "" }, + { "nv!ogl_c5e9d7a3", "" }, + { "nv!ogl_c5e9d7a4", "" }, + { "nv!ogl_c5e9d7b4", "" }, + { "nv!ogl_c618f9", "" }, + { "nv!ogl_ca345840", "" }, + { "nv!ogl_cachedisable", "" }, + { "nv!ogl_channelpriorityoverride", "" }, + { "nv!ogl_cleardatastorevidmem", "" }, + { "nv!ogl_cmdbufmemoryspaceenables", "" }, + { "nv!ogl_cmdbufminwords", "" }, + { "nv!ogl_cmdbufsizewords", "" }, + { "nv!ogl_conformantblitframebufferscissor", "" }, + { "nv!ogl_conformantincompletetextures", "" }, + { "nv!ogl_copybuffermethod", "" }, + { "nv!ogl_cubemapaniso", "" }, + { "nv!ogl_cubemapfiltering", "" }, + { "nv!ogl_d0e9a4d7", "" }, + { "nv!ogl_d13733f12", "" }, + { "nv!ogl_d1b399", "" }, + { "nv!ogl_d2983c32", "" }, + { "nv!ogl_d2983c33", "" }, + { "nv!ogl_d2e71b", "" }, + { "nv!ogl_d377dc", "" }, + { "nv!ogl_d377dd", "" }, + { "nv!ogl_d489f4", "" }, + { "nv!ogl_d4bce1", "" }, + { "nv!ogl_d518cb", "" }, + { "nv!ogl_d518cd", "" }, + { "nv!ogl_d518ce", "" }, + { "nv!ogl_d518d0", "" }, + { "nv!ogl_d518d1", "" }, + { "nv!ogl_d518d2", "" }, + { "nv!ogl_d518d3", "" }, + { "nv!ogl_d518d4", "" }, + { "nv!ogl_d518d5", "" }, + { "nv!ogl_d59eda", "" }, + { "nv!ogl_d83cbd", "" }, + { "nv!ogl_d8e777", "" }, + { "nv!ogl_debug_level", "" }, + { "nv!ogl_debug_mask", "" }, + { "nv!ogl_debug_options", "" }, + { "nv!ogl_devshmpageableallocations", "" }, + { "nv!ogl_df1f9812", "" }, + { "nv!ogl_df783c", "" }, + { "nv!ogl_diagenable", "" }, + { "nv!ogl_disallowcemask", "" }, + { "nv!ogl_disallowz16", "" }, + { "nv!ogl_dlmemoryspaceenables", "" }, + { "nv!ogl_e0bfec", "" }, + { "nv!ogl_e433456d", "" }, + { "nv!ogl_e435563f", "" }, + { "nv!ogl_e4cd9c", "" }, + { "nv!ogl_e5c972", "" }, + { "nv!ogl_e639ef", "" }, + { "nv!ogl_e802af", "" }, + { "nv!ogl_eae964", "" }, + { "nv!ogl_earlytexturehwallocation", "" }, + { "nv!ogl_eb92a3", "" }, + { "nv!ogl_ebca56", "" }, + { "nv!ogl_expert_detail_level", "" }, + { "nv!ogl_expert_output_mask", "" }, + { "nv!ogl_expert_report_mask", "" }, + { "nv!ogl_extensionstringnvarch", "" }, + { "nv!ogl_extensionstringversion", "" }, + { "nv!ogl_f00f1938", "" }, + { "nv!ogl_f10736", "" }, + { "nv!ogl_f1846870", "" }, + { "nv!ogl_f33bc370", "" }, + { "nv!ogl_f392a874", "" }, + { "nv!ogl_f49ae8", "" }, + { "nv!ogl_fa345cce", "" }, + { "nv!ogl_fa35cc4", "" }, + { "nv!ogl_faa14a", "" }, + { "nv!ogl_faf8a723", "" }, + { "nv!ogl_fastgs", "" }, + { "nv!ogl_fbf4ac45", "" }, + { "nv!ogl_fbo_blit_ignore_srgb", "" }, + { "nv!ogl_fc64c7", "" }, + { "nv!ogl_ff54ec97", "" }, + { "nv!ogl_ff54ec98", "" }, + { "nv!ogl_forceexitprocessdetach", "" }, + { "nv!ogl_forcerequestedesversion", "" }, + { "nv!ogl_glsynctovblank", "" }, + { "nv!ogl_gvitimeoutcontrol", "" }, + { "nv!ogl_hcctrl", "" }, + { "nv!ogl_hwstate_per_ctx", "" }, + { "nv!ogl_machinecachelimit", "" }, + { "nv!ogl_maxframesallowed", "" }, + { "nv!ogl_memmgrcachedalloclimit", "" }, + { "nv!ogl_memmgrcachedalloclimitratio", "" }, + { "nv!ogl_memmgrsysheapalloclimit", "" }, + { "nv!ogl_memmgrsysheapalloclimitratio", "" }, + { "nv!ogl_memmgrvidheapalloclimit", "" }, + { "nv!ogl_mosaic_clip_to_subdev", "" }, + { "nv!ogl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!ogl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!ogl_overlaymergeblittimerms", "" }, + { "nv!ogl_perfmon_mode", "" }, + { "nv!ogl_pixbar_mode", "" }, + { "nv!ogl_qualityenhancements", "" }, + { "nv!ogl_r27s18q28", "" }, + { "nv!ogl_r2d7c1d8", "" }, + { "nv!ogl_renderer", "" }, + { "nv!ogl_renderqualityflags", "" }, + { "nv!ogl_s3tcquality", "" }, + { "nv!ogl_shaderatomics", "" }, + { "nv!ogl_shadercacheinitsize", "" }, + { "nv!ogl_shader_disk_cache_path", "" }, + { "nv!ogl_shader_disk_cache_read_only", "" }, + { "nv!ogl_shaderobjects", "" }, + { "nv!ogl_shaderportabilitywarnings", "" }, + { "nv!ogl_shaderwarningsaserrors", "" }, + { "nv!ogl_skiptexturehostcopies", "" }, + { "nv!ogl_sli_dli_control", "" }, + { "nv!ogl_sparsetexture", "" }, + { "nv!ogl_spinlooptimeout", "" }, + { "nv!ogl_sync_to_vblank", "" }, + { "nv!ogl_sysheapreuseratio", "" }, + { "nv!ogl_sysmemtexturepromotion", "" }, + { "nv!ogl_targetflushcount", "" }, + { "nv!ogl_tearingfreeswappresent", "" }, + { "nv!ogl_texclampbehavior", "" }, + { "nv!ogl_texlodbias", "" }, + { "nv!ogl_texmemoryspaceenables", "" }, + { "nv!ogl_textureprecache", "" }, + { "nv!ogl_threadcontrol", "" }, + { "nv!ogl_threadcontrol2", "" }, + { "nv!ogl_usegvievents", "" }, + { "nv!ogl_vbomemoryspaceenables", "" }, + { "nv!ogl_vertexlimit", "" }, + { "nv!ogl_vidheapreuseratio", "" }, + { "nv!ogl_vpipe", "" }, + { "nv!ogl_vpipeformatbloatlimit", "" }, + { "nv!ogl_wglmessageboxonabort", "" }, + { "nv!ogl_writeinfolog", "" }, + { "nv!ogl_writeprogramobjectassembly", "" }, + { "nv!ogl_writeprogramobjectsource", "" }, + { "nv!ogl_xnvadapterpresent", "" }, + { "nv!ogl_yield", "" }, + { "nv!ogl_yieldfunction", "" }, + { "nv!ogl_yieldfunctionfast", "" }, + { "nv!ogl_yieldfunctionslow", "" }, + { "nv!ogl_yieldfunctionwaitfordcqueue", "" }, + { "nv!ogl_yieldfunctionwaitforframe", "" }, + { "nv!ogl_yieldfunctionwaitforgpu", "" }, + { "nv!ogl_zbctableaddhysteresis", "" }, + { "nv!overlaymergeblittimerms", "" }, + { "nv!perfmon_mode", "" }, + { "nv!persist.sys.display.resolution", "" }, + { "nv!persist.tegra.composite.fallb", "" }, + { "nv!persist.tegra.composite.policy", "" }, + { "nv!persist.tegra.composite.range", "" }, + { "nv!persist.tegra.compositor", "" }, + { "nv!persist.tegra.compositor.virt", "" }, + { "nv!persist.tegra.compression", "" }, + { "nv!persist.tegra.cursor.enable", "" }, + { "nv!persist.tegra.didim.enable", "" }, + { "nv!persist.tegra.didim.normal", "" }, + { "nv!persist.tegra.didim.video", "" }, + { "nv!persist.tegra.disp.heads", "" }, + { "nv!persist.tegra.gamma_correction", "" }, + { "nv!persist.tegra.gpu_mapping_cache", "" }, + { "nv!persist.tegra.grlayout", "" }, + { "nv!persist.tegra.hdmi.2020.10", "" }, + { "nv!persist.tegra.hdmi.2020.fake", "" }, + { "nv!persist.tegra.hdmi.2020.force", "" }, + { "nv!persist.tegra.hdmi.autorotate", "" }, + { "nv!persist.tegra.hdmi.hdr.fake", "" }, + { "nv!persist.tegra.hdmi.ignore_ratio", "" }, + { "nv!persist.tegra.hdmi.limit.clock", "" }, + { "nv!persist.tegra.hdmi.only_16_9", "" }, + { "nv!persist.tegra.hdmi.range", "" }, + { "nv!persist.tegra.hdmi.resolution", "" }, + { "nv!persist.tegra.hdmi.underscan", "" }, + { "nv!persist.tegra.hdmi.yuv.422", "" }, + { "nv!persist.tegra.hdmi.yuv.444", "" }, + { "nv!persist.tegra.hdmi.yuv.enable", "" }, + { "nv!persist.tegra.hdmi.yuv.force", "" }, + { "nv!persist.tegra.hwc.nvdc", "" }, + { "nv!persist.tegra.idle.minimum_fps", "" }, + { "nv!persist.tegra.panel.rotation", "" }, + { "nv!persist.tegra.scan_props", "" }, + { "nv!persist.tegra.stb.mode", "" }, + { "nv!persist.tegra.zbc_override", "" }, + { "nv!pixbar_mode", "" }, + { "nv!qualityenhancements", "" }, + { "nv!r27s18q28", "" }, + { "nv!r2d7c1d8", "" }, + { "nv!renderer", "" }, + { "nv!renderqualityflags", "" }, + { "nv!rmos_debug_mask", "" }, + { "nv!rmos_set_production_mode", "" }, + { "nv!s3tcquality", "" }, + { "nv!shaderatomics", "" }, + { "nv!shadercacheinitsize", "" }, + { "nv!shader_disk_cache_path", "" }, + { "nv!shader_disk_cache_read_only", "" }, + { "nv!shaderobjects", "" }, + { "nv!shaderportabilitywarnings", "" }, + { "nv!shaderwarningsaserrors", "" }, + { "nv!skiptexturehostcopies", "" }, + { "nv!sli_dli_control", "" }, + { "nv!sparsetexture", "" }, + { "nv!spinlooptimeout", "" }, + { "nv!sync_to_vblank", "" }, + { "nv!sysheapreuseratio", "" }, + { "nv!sysmemtexturepromotion", "" }, + { "nv!targetflushcount", "" }, + { "nv!tearingfreeswappresent", "" }, + { "nv!tegra.refresh", "" }, + { "nv!texclampbehavior", "" }, + { "nv!texlodbias", "" }, + { "nv!texmemoryspaceenables", "" }, + { "nv!textureprecache", "" }, + { "nv!threadcontrol", "" }, + { "nv!threadcontrol2", "" }, + { "nv!tvmr.avp.logs", "" }, + { "nv!tvmr.buffer.logs", "" }, + { "nv!tvmr.dec.prof", "" }, + { "nv!tvmr.deint.logs", "" }, + { "nv!tvmr.dfs.logs", "" }, + { "nv!tvmr.ffprof.logs", "" }, + { "nv!tvmr.game.stream", "" }, + { "nv!tvmr.general.logs", "" }, + { "nv!tvmr.input.dump", "" }, + { "nv!tvmr.seeking.logs", "" }, + { "nv!tvmr.ts_pulldown", "" }, + { "nv!usegvievents", "" }, + { "nv!vbomemoryspaceenables", "" }, + { "nv!vcc_debug_ip", "" }, + { "nv!vcc_verbose_level", "" }, + { "nv!vertexlimit", "" }, + { "nv!viccomposer.filter", "" }, + { "nv!videostats-enable", "" }, + { "nv!vidheapreuseratio", "" }, + { "nv!vpipe", "" }, + { "nv!vpipeformatbloatlimit", "" }, + { "nv!wglmessageboxonabort", "" }, + { "nv!writeinfolog", "" }, + { "nv!writeprogramobjectassembly", "" }, + { "nv!writeprogramobjectsource", "" }, + { "nv!xnvadapterpresent", "" }, + { "nv!yield", "" }, + { "nv!yieldfunction", "" }, + { "nv!yieldfunctionfast", "" }, + { "nv!yieldfunctionslow", "" }, + { "nv!yieldfunctionwaitfordcqueue", "" }, + { "nv!yieldfunctionwaitforframe", "" }, + { "nv!yieldfunctionwaitforgpu", "" }, + { "nv!zbctableaddhysteresis", "" }, + { "pcm!enable", true }, + { "pctl!intermittent_task_interval_seconds", 21600 }, + { "prepo!devmenu_prepo_page_view", false }, + { "prepo!background_processing", true }, + { "prepo!transmission_interval_min", 10 }, + { "prepo!transmission_retry_interval", 3600 }, + { "psm!evaluation_log_enabled", false }, + { "snap_shot_dump!auto_dump", false }, + { "snap_shot_dump!output_dir", "%USERPROFILE%/Documents/Nintendo/NXDMP" }, + { "snap_shot_dump!full_dump", false }, + { "systemconfig!field_testing", false }, + { "systemconfig!exhivision", false }, + { "systempowerstate!always_reboot", false }, + { "systempowerstate!power_state_message_emulation_trigger_time", 0 }, + { "systempowerstate!power_state_message_to_emulate", 0 }, + { "target_manager!device_name", "" }, + { "vulnerability!needs_update_vulnerability_policy", 0 }, + { "apm!performance_mode_policy", "auto" }, + { "apm!sdev_throttling_enabled", true }, + { "apm!sdev_throttling_additional_delay_us", 16000 }, + { "apm!battery_draining_enabled", false }, + { "apm!sdev_cpu_overclock_enabled", false }, + { "bcat!production_mode", true }, + { "bpc!enable_quasi_off", true }, + { "bsp0!usb", "UDS" }, + { "bsp0!tm_transport", "USB" }, + { "bluetooth_debug!skip_boot", false }, + { "contents_delivery!enable_debug_api", false }, + { "eupld!upload_enabled", true }, + { "fatal!transition_to_fatal", true }, + { "fatal!show_extra_info", false }, + { "gpu_core_dump!auto_dump", false }, + { "hid_debug!enables_debugpad", false }, + { "hid_debug!manages_devices", true }, + { "hid_debug!emulate_future_device", false }, + { "hid_debug!emulate_firmware_update_failure", false }, + { "hid_debug!emulate_mcu_hardware_error", false }, + { "hid_debug!firmware_update_failure_emulation_mode", 0 }, + { "jit_debug!enable_jit_debug", false }, + { "npns!background_processing", true }, + { "npns!logmanager_redirection", true }, + { "npns!sleep_processing_timeout", 30 }, + { "npns!sleep_periodic_interval", 10800 }, + { "npns!sleep_max_try_count", 5 }, + { "npns!test_mode", false }, + { "ns.applet!overlay_applet_id", "0x010000000000100c" }, + { "ns.applet!system_applet_id", "0x0100000000001000" }, + { "ns.applet!shop_applet_id", "0x010000000000100b" }, + { "ns.autoboot!enabled", true }, + { "ns.pseudodeviceid!reset_pseudo_device_id", false }, + { "nsd!environment_identifier", "lp1" }, + { "nsd!test_mode", false }, + { "ntc!is_autonomic_correction_enabled", true }, + { "ntc!autonomic_correction_interval_seconds", 432000 }, + { "ntc!autonomic_correction_failed_retry_interval_seconds", 1800 }, + { "ntc!autonomic_correction_immediate_try_count_max", 4 }, + { "ntc!autonomic_correction_immediate_try_interval_milliseconds", 5000 }, + { "nv!nv_graphics_firmware_memory_margin", false }, + { "omm!operation_mode_policy", "auto" }, + { "omm!sleep_fade_in_ms", 50 }, + { "omm!sleep_fade_out_ms", 100 }, + { "omm!charging_sign_ms", 3000 }, + { "omm!low_battery_sign_ms", 3000 }, + { "omm!sign_fade_in_ms", 0 }, + { "omm!sign_fade_out_ms", 400 }, + { "omm!sign_wait_layer_visible_ms", 100 }, + { "omm!startup_fade_in_ms", 200 }, + { "omm!startup_fade_out_ms", 400 }, + { "omm!backlight_off_ms_on_handheld_switch", 150 }, + { "omm!sleep_on_ac_ok_boot", true }, + { "pdm!save_playlog", true }, + { "productinfo!product_name", "Nintendo Switch" }, + { "productinfo!cec_osd_name", "NintendoSwitch" }, + { "ro!ease_nro_restriction", false }, + { "settings_debug!is_debug_mode_enabled", false }, + { "systemreport!enabled", true }, + { "systemsleep!enter_sleep", true }, + { "systemsleep!enter_sc7", true }, + { "systemsleep!keep_vdd_core", true }, + { "systemsleep!disable_tma_sleep", false }, + { "systemsleep!disable_auto_sleep", false }, + { "systemsleep!override_auto_sleep_time", 0 }, + { "systemsleep!sleep_pending_time_ms", 15000 }, + { "systemsleep!hush_time_after_brief_power_button_press_ms", 1000 }, + { "systemsleep!transition_timeout_sec", 60 }, + { "systemsleep!dummy_event_auto_wake", false }, + { "systemupdate!debug_id", "0x0000000000000000" }, + { "systemupdate!debug_version", 0 }, + { "systemupdate!bgnup_retry_seconds", 60 }, + { "systemupdate!enable_background_download_stress_testing", false }, + { "systemupdate!debug_id_for_content_delivery", "0x0000000000000000" }, + { "systemupdate!debug_version_for_content_delivery", 0 }, + { "systemupdate!assumed_system_applet_version", 0 }, + { "tc!iir_filter_gain_soc", 100 }, + { "tc!iir_filter_gain_pcb", 100 }, + { "tc!tskin_soc_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_soc_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_pcb_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_pcb_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_select", "both" }, + { "tc!tskin_rate_table_handheld", "[[-1000000, 40000, 0, 0], [36000, 43000, 51, 51], [43000, 48000, 51, 102], [48000, 53000, 102, 153], [53000, 1000000, 153, 153], [48000, 1000000, 153, 153]]" }, + { "tc!tskin_rate_table_console", "[[-1000000, 43000, 51, 51], [43000, 53000, 51, 153], [53000, 58000, 153, 255], [58000, 1000000, 255, 255]]" }, + { "tc!rate_select", "both" }, + { "tc!log_enabled", false }, + { "tc!sleep_enabled", true }, + { "time!standard_steady_clock_test_offset_minutes", 0 }, + { "time!standard_steady_clock_rtc_update_interval_minutes", 5 }, + { "time!standard_network_clock_sufficient_accuracy_minutes", 43200 }, + { "time!standard_user_clock_initial_year", 2019 }, + { "usb!usb30_force_enabled", false }, + { "wlan_debug!skip_wlan_boot", false } + }; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs new file mode 100644 index 00000000..8b0fde6c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs @@ -0,0 +1,126 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + enum ResultCode + { + ModuleId = 105, + ErrorCodeShift = 9, + + Success = 0, + + NullSettingsName = (201 << ErrorCodeShift) | ModuleId, + NullSettingsKey = (202 << ErrorCodeShift) | ModuleId, + NullSettingsValue = (203 << ErrorCodeShift) | ModuleId, + NullSettingsValueBuffer = (205 << ErrorCodeShift) | ModuleId, + NullSettingValueSizeBuffer = (208 << ErrorCodeShift) | ModuleId, + NullDebugModeFlagBuffer = (209 << ErrorCodeShift) | ModuleId, + SettingGroupNameHasZeroLength = (221 << ErrorCodeShift) | ModuleId, + EmptySettingsItemKey = (222 << ErrorCodeShift) | ModuleId, + SettingGroupNameIsTooLong = (241 << ErrorCodeShift) | ModuleId, + SettingNameIsTooLong = (242 << ErrorCodeShift) | ModuleId, + SettingGroupNameEndsWithDotOrContainsInvalidCharacters = (261 << ErrorCodeShift) | ModuleId, + SettingNameEndsWithDotOrContainsInvalidCharacters = (262 << ErrorCodeShift) | ModuleId, + NullLanguageCodeBuffer = (621 << ErrorCodeShift) | ModuleId, + LanguageOutOfRange = (625 << ErrorCodeShift) | ModuleId, + NullNetworkSettingsBuffer = (631 << ErrorCodeShift) | ModuleId, + NullNetworkSettingsOutputCountBuffer = (632 << ErrorCodeShift) | ModuleId, + NullBacklightSettingsBuffer = (641 << ErrorCodeShift) | ModuleId, + NullBluetoothDeviceSettingBuffer = (651 << ErrorCodeShift) | ModuleId, + NullBluetoothDeviceSettingOutputCountBuffer = (652 << ErrorCodeShift) | ModuleId, + NullBluetoothEnableFlagBuffer = (653 << ErrorCodeShift) | ModuleId, + NullBluetoothAFHEnableFlagBuffer = (654 << ErrorCodeShift) | ModuleId, + NullBluetoothBoostEnableFlagBuffer = (655 << ErrorCodeShift) | ModuleId, + NullBLEPairingSettingsBuffer = (656 << ErrorCodeShift) | ModuleId, + NullBLEPairingSettingsEntryCountBuffer = (657 << ErrorCodeShift) | ModuleId, + NullExternalSteadyClockSourceIDBuffer = (661 << ErrorCodeShift) | ModuleId, + NullUserSystemClockContextBuffer = (662 << ErrorCodeShift) | ModuleId, + NullNetworkSystemClockContextBuffer = (663 << ErrorCodeShift) | ModuleId, + NullUserSystemClockAutomaticCorrectionEnabledFlagBuffer = (664 << ErrorCodeShift) | ModuleId, + NullShutdownRTCValueBuffer = (665 << ErrorCodeShift) | ModuleId, + NullExternalSteadyClockInternalOffsetBuffer = (666 << ErrorCodeShift) | ModuleId, + NullAccountSettingsBuffer = (671 << ErrorCodeShift) | ModuleId, + NullAudioVolumeBuffer = (681 << ErrorCodeShift) | ModuleId, + NullForceMuteOnHeadphoneRemovedBuffer = (683 << ErrorCodeShift) | ModuleId, + NullHeadphoneVolumeWarningCountBuffer = (684 << ErrorCodeShift) | ModuleId, + InvalidAudioOutputMode = (687 << ErrorCodeShift) | ModuleId, + NullHeadphoneVolumeUpdateFlagBuffer = (688 << ErrorCodeShift) | ModuleId, + NullConsoleInformationUploadFlagBuffer = (691 << ErrorCodeShift) | ModuleId, + NullAutomaticApplicationDownloadFlagBuffer = (701 << ErrorCodeShift) | ModuleId, + NullNotificationSettingsBuffer = (702 << ErrorCodeShift) | ModuleId, + NullAccountNotificationSettingsEntryCountBuffer = (703 << ErrorCodeShift) | ModuleId, + NullAccountNotificationSettingsBuffer = (704 << ErrorCodeShift) | ModuleId, + NullVibrationMasterVolumeBuffer = (711 << ErrorCodeShift) | ModuleId, + NullNXControllerSettingsBuffer = (712 << ErrorCodeShift) | ModuleId, + NullNXControllerSettingsEntryCountBuffer = (713 << ErrorCodeShift) | ModuleId, + NullUSBFullKeyEnableFlagBuffer = (714 << ErrorCodeShift) | ModuleId, + NullTVSettingsBuffer = (721 << ErrorCodeShift) | ModuleId, + NullEDIDBuffer = (722 << ErrorCodeShift) | ModuleId, + NullDataDeletionSettingsBuffer = (731 << ErrorCodeShift) | ModuleId, + NullInitialSystemAppletProgramIDBuffer = (741 << ErrorCodeShift) | ModuleId, + NullOverlayDispProgramIDBuffer = (742 << ErrorCodeShift) | ModuleId, + NullIsInRepairProcessBuffer = (743 << ErrorCodeShift) | ModuleId, + NullRequiresRunRepairTimeReviserBuffer = (744 << ErrorCodeShift) | ModuleId, + NullDeviceTimezoneLocationNameBuffer = (751 << ErrorCodeShift) | ModuleId, + NullPrimaryAlbumStorageBuffer = (761 << ErrorCodeShift) | ModuleId, + NullUSB30EnableFlagBuffer = (771 << ErrorCodeShift) | ModuleId, + NullUSBTypeCPowerSourceCircuitVersionBuffer = (772 << ErrorCodeShift) | ModuleId, + NullBatteryLotBuffer = (781 << ErrorCodeShift) | ModuleId, + NullSerialNumberBuffer = (791 << ErrorCodeShift) | ModuleId, + NullLockScreenFlagBuffer = (801 << ErrorCodeShift) | ModuleId, + NullColorSetIDBuffer = (803 << ErrorCodeShift) | ModuleId, + NullQuestFlagBuffer = (804 << ErrorCodeShift) | ModuleId, + NullWirelessCertificationFileSizeBuffer = (805 << ErrorCodeShift) | ModuleId, + NullWirelessCertificationFileBuffer = (806 << ErrorCodeShift) | ModuleId, + NullInitialLaunchSettingsBuffer = (807 << ErrorCodeShift) | ModuleId, + NullDeviceNicknameBuffer = (808 << ErrorCodeShift) | ModuleId, + NullBatteryPercentageFlagBuffer = (809 << ErrorCodeShift) | ModuleId, + NullAppletLaunchFlagsBuffer = (810 << ErrorCodeShift) | ModuleId, + NullWirelessLANEnableFlagBuffer = (1012 << ErrorCodeShift) | ModuleId, + NullProductModelBuffer = (1021 << ErrorCodeShift) | ModuleId, + NullNFCEnableFlagBuffer = (1031 << ErrorCodeShift) | ModuleId, + NullECIDeviceCertificateBuffer = (1041 << ErrorCodeShift) | ModuleId, + NullETicketDeviceCertificateBuffer = (1042 << ErrorCodeShift) | ModuleId, + NullSleepSettingsBuffer = (1051 << ErrorCodeShift) | ModuleId, + NullEULAVersionBuffer = (1061 << ErrorCodeShift) | ModuleId, + NullEULAVersionEntryCountBuffer = (1062 << ErrorCodeShift) | ModuleId, + NullLDNChannelBuffer = (1071 << ErrorCodeShift) | ModuleId, + NullSSLKeyBuffer = (1081 << ErrorCodeShift) | ModuleId, + NullSSLCertificateBuffer = (1082 << ErrorCodeShift) | ModuleId, + NullTelemetryFlagsBuffer = (1091 << ErrorCodeShift) | ModuleId, + NullGamecardKeyBuffer = (1101 << ErrorCodeShift) | ModuleId, + NullGamecardCertificateBuffer = (1102 << ErrorCodeShift) | ModuleId, + NullPTMBatteryLotBuffer = (1111 << ErrorCodeShift) | ModuleId, + NullPTMFuelGaugeParameterBuffer = (1112 << ErrorCodeShift) | ModuleId, + NullECIDeviceKeyBuffer = (1121 << ErrorCodeShift) | ModuleId, + NullETicketDeviceKeyBuffer = (1122 << ErrorCodeShift) | ModuleId, + NullSpeakerParameterBuffer = (1131 << ErrorCodeShift) | ModuleId, + NullFirmwareVersionBuffer = (1141 << ErrorCodeShift) | ModuleId, + NullFirmwareVersionDigestBuffer = (1142 << ErrorCodeShift) | ModuleId, + NullRebootlessSystemUpdateVersionBuffer = (1143 << ErrorCodeShift) | ModuleId, + NullMiiAuthorIDBuffer = (1151 << ErrorCodeShift) | ModuleId, + NullFatalFlagsBuffer = (1161 << ErrorCodeShift) | ModuleId, + NullAutoUpdateEnableFlagBuffer = (1171 << ErrorCodeShift) | ModuleId, + NullExternalRTCResetFlagBuffer = (1181 << ErrorCodeShift) | ModuleId, + NullPushNotificationActivityModeBuffer = (1191 << ErrorCodeShift) | ModuleId, + NullServiceDiscoveryControlSettingBuffer = (1201 << ErrorCodeShift) | ModuleId, + NullErrorReportSharePermissionBuffer = (1211 << ErrorCodeShift) | ModuleId, + NullLCDVendorIDBuffer = (1221 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAccelerationBiasBuffer = (1231 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityBiasBuffer = (1232 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAccelerationGainBuffer = (1233 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityGainBuffer = (1234 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityTimeBiasBuffer = (1235 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularAccelerationBuffer = (1236 << ErrorCodeShift) | ModuleId, + NullKeyboardLayoutBuffer = (1241 << ErrorCodeShift) | ModuleId, + InvalidKeyboardLayout = (1245 << ErrorCodeShift) | ModuleId, + NullWebInspectorFlagBuffer = (1251 << ErrorCodeShift) | ModuleId, + NullAllowedSSLHostsBuffer = (1252 << ErrorCodeShift) | ModuleId, + NullAllowedSSLHostsEntryCountBuffer = (1253 << ErrorCodeShift) | ModuleId, + NullHostFSMountPointBuffer = (1254 << ErrorCodeShift) | ModuleId, + NullAmiiboKeyBuffer = (1271 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVCertificateBuffer = (1272 << ErrorCodeShift) | ModuleId, + NullAmiiboECDSACertificateBuffer = (1273 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSKeyBuffer = (1274 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSCertificateBuffer = (1275 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSRootCertificateBuffer = (1276 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs new file mode 100644 index 00000000..b8ef8e8e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Settings.Types +{ + enum PlatformRegion + { + Global = 1, + China = 2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs new file mode 100644 index 00000000..f867f23a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + [Service("sm:m")] + class IManagerInterface : IpcService + { + public IManagerInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs new file mode 100644 index 00000000..005ec32d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -0,0 +1,261 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + class IUserInterface : IpcService + { + private static Dictionary<string, Type> _services; + + private readonly SmRegistry _registry; + private readonly ServerBase _commonServer; + + private bool _isInitialized; + + public IUserInterface(KernelContext context, SmRegistry registry) + { + _commonServer = new ServerBase(context, "CommonServer"); + _registry = registry; + } + + static IUserInterface() + { + _services = Assembly.GetExecutingAssembly().GetTypes() + .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) + .Select(service => (((ServiceAttribute)service).Name, type))) + .ToDictionary(service => service.Name, service => service.type); + } + + [CommandCmif(0)] + [CommandTipc(0)] // 12.0.0+ + // Initialize(pid, u64 reserved) + public ResultCode Initialize(ServiceCtx context) + { + _isInitialized = true; + + return ResultCode.Success; + } + + [CommandTipc(1)] // 12.0.0+ + // GetService(ServiceName name) -> handle<move, session> + public ResultCode GetServiceTipc(ServiceCtx context) + { + context.Response.HandleDesc = IpcHandleDesc.MakeMove(0); + + return GetService(context); + } + + [CommandCmif(1)] + public ResultCode GetService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + string name = ReadName(context); + + if (name == string.Empty) + { + return ResultCode.InvalidName; + } + + KSession session = new KSession(context.Device.System.KernelContext); + + if (_registry.TryGetService(name, out KPort port)) + { + Result result = port.EnqueueIncomingSession(session.ServerSession); + + if (result != Result.Success) + { + throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + } + else + { + if (_services.TryGetValue(name, out Type type)) + { + ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name); + + IpcService service = serviceAttribute.Parameter != null + ? (IpcService)Activator.CreateInstance(type, context, serviceAttribute.Parameter) + : (IpcService)Activator.CreateInstance(type, context); + + service.TrySetServer(_commonServer); + service.Server.AddSessionObj(session.ServerSession, service); + } + else + { + if (context.Device.Configuration.IgnoreMissingServices) + { + Logger.Warning?.Print(LogClass.Service, $"Missing service {name} ignored"); + } + else + { + throw new NotImplementedException(name); + } + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // RegisterService(ServiceName name, u8 isLight, u32 maxHandles) -> handle<move, port> + public ResultCode RegisterServiceCmif(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + return RegisterService(context, name, isLight, maxSessions); + } + + [CommandTipc(2)] // 12.0.0+ + // RegisterService(ServiceName name, u32 maxHandles, u8 isLight) -> handle<move, port> + public ResultCode RegisterServiceTipc(ServiceCtx context) + { + if (!_isInitialized) + { + context.Response.HandleDesc = IpcHandleDesc.MakeMove(0); + + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + int maxSessions = context.RequestData.ReadInt32(); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + return RegisterService(context, name, isLight, maxSessions); + } + + private ResultCode RegisterService(ServiceCtx context, string name, bool isLight, int maxSessions) + { + if (string.IsNullOrEmpty(name)) + { + return ResultCode.InvalidName; + } + + Logger.Debug?.Print(LogClass.ServiceSm, $"Register \"{name}\"."); + + KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, null); + + if (!_registry.TryRegister(name, port)) + { + return ResultCode.AlreadyRegistered; + } + + if (context.Process.HandleTable.GenerateHandle(port.ServerPort, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + [CommandTipc(3)] // 12.0.0+ + // UnregisterService(ServiceName name) + public ResultCode UnregisterService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + if (string.IsNullOrEmpty(name)) + { + return ResultCode.InvalidName; + } + + if (!_registry.Unregister(name)) + { + return ResultCode.NotRegistered; + } + + return ResultCode.Success; + } + + private static string ReadName(ServiceCtx context) + { + string name = string.Empty; + + for (int index = 0; index < 8 && + context.RequestData.BaseStream.Position < + context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + name += (char)chr; + } + } + + return name; + } + + public override void DestroyAtExit() + { + _commonServer.Dispose(); + + base.DestroyAtExit(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs new file mode 100644 index 00000000..f72bf010 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + enum ResultCode + { + ModuleId = 21, + ErrorCodeShift = 9, + + Success = 0, + + NotInitialized = (2 << ErrorCodeShift) | ModuleId, + AlreadyRegistered = (4 << ErrorCodeShift) | ModuleId, + InvalidName = (6 << ErrorCodeShift) | ModuleId, + NotRegistered = (7 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs b/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs new file mode 100644 index 00000000..e62e0eb5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.HOS.Kernel.Ipc; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + class SmRegistry + { + private readonly ConcurrentDictionary<string, KPort> _registeredServices; + private readonly AutoResetEvent _serviceRegistrationEvent; + + public SmRegistry() + { + _registeredServices = new ConcurrentDictionary<string, KPort>(); + _serviceRegistrationEvent = new AutoResetEvent(false); + } + + public bool TryGetService(string name, out KPort port) + { + return _registeredServices.TryGetValue(name, out port); + } + + public bool TryRegister(string name, KPort port) + { + if (_registeredServices.TryAdd(name, port)) + { + _serviceRegistrationEvent.Set(); + return true; + } + + return false; + } + + public bool Unregister(string name) + { + return _registeredServices.TryRemove(name, out _); + } + + public bool IsServiceRegistered(string name) + { + return _registeredServices.TryGetValue(name, out _); + } + + public void WaitForServiceRegistration() + { + _serviceRegistrationEvent.WaitOne(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs new file mode 100644 index 00000000..a93f176a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs @@ -0,0 +1,184 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class BsdContext + { + private static ConcurrentDictionary<ulong, BsdContext> _registry = new ConcurrentDictionary<ulong, BsdContext>(); + + private readonly object _lock = new object(); + + private List<IFileDescriptor> _fds; + + private BsdContext() + { + _fds = new List<IFileDescriptor>(); + } + + public ISocket RetrieveSocket(int socketFd) + { + IFileDescriptor file = RetrieveFileDescriptor(socketFd); + + if (file is ISocket socket) + { + return socket; + } + + return null; + } + + public IFileDescriptor RetrieveFileDescriptor(int fd) + { + lock (_lock) + { + if (fd >= 0 && _fds.Count > fd) + { + return _fds[fd]; + } + } + + return null; + } + + public List<IFileDescriptor> RetrieveFileDescriptorsFromMask(ReadOnlySpan<byte> mask) + { + List<IFileDescriptor> fds = new(); + + for (int i = 0; i < mask.Length; i++) + { + byte current = mask[i]; + + while (current != 0) + { + int bit = BitOperations.TrailingZeroCount(current); + current &= (byte)~(1 << bit); + int fd = i * 8 + bit; + + fds.Add(RetrieveFileDescriptor(fd)); + } + } + + return fds; + } + + public int RegisterFileDescriptor(IFileDescriptor file) + { + lock (_lock) + { + for (int fd = 0; fd < _fds.Count; fd++) + { + if (_fds[fd] == null) + { + _fds[fd] = file; + + return fd; + } + } + + _fds.Add(file); + + return _fds.Count - 1; + } + } + + public void BuildMask(List<IFileDescriptor> fds, Span<byte> mask) + { + foreach (IFileDescriptor descriptor in fds) + { + int fd = _fds.IndexOf(descriptor); + + mask[fd >> 3] |= (byte)(1 << (fd & 7)); + } + } + + public int DuplicateFileDescriptor(int fd) + { + IFileDescriptor oldFile = RetrieveFileDescriptor(fd); + + if (oldFile != null) + { + lock (_lock) + { + oldFile.Refcount++; + + return RegisterFileDescriptor(oldFile); + } + } + + return -1; + } + + public bool CloseFileDescriptor(int fd) + { + IFileDescriptor file = RetrieveFileDescriptor(fd); + + if (file != null) + { + file.Refcount--; + + if (file.Refcount <= 0) + { + file.Dispose(); + } + + lock (_lock) + { + _fds[fd] = null; + } + + return true; + } + + return false; + } + + public LinuxError ShutdownAllSockets(BsdSocketShutdownFlags how) + { + lock (_lock) + { + foreach (IFileDescriptor file in _fds) + { + if (file is ISocket socket) + { + LinuxError errno = socket.Shutdown(how); + + if (errno != LinuxError.SUCCESS) + { + return errno; + } + } + } + } + + return LinuxError.SUCCESS; + } + + public static BsdContext GetOrRegister(ulong processId) + { + BsdContext context = GetContext(processId); + + if (context == null) + { + context = new BsdContext(); + + _registry.TryAdd(processId, context); + } + + return context; + } + + public static BsdContext GetContext(ulong processId) + { + if (!_registry.TryGetValue(processId, out BsdContext processContext)) + { + return null; + } + + return processContext; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs new file mode 100644 index 00000000..b63864c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -0,0 +1,1121 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsd:s", true)] + [Service("bsd:u", false)] + class IClient : IpcService + { + private static readonly List<IPollManager> _pollManagers = new List<IPollManager> + { + EventFileDescriptorPollManager.Instance, + ManagedSocketPollManager.Instance + }; + + private BsdContext _context; + private bool _isPrivileged; + + public IClient(ServiceCtx context, bool isPrivileged) : base(context.Device.System.BsdServer) + { + _isPrivileged = isPrivileged; + } + + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = LinuxError.SUCCESS) + { + if (errorCode != LinuxError.SUCCESS) + { + result = -1; + } + + context.ResponseData.Write(result); + context.ResponseData.Write((int)errorCode); + + return ResultCode.Success; + } + + private static AddressFamily ConvertBsdAddressFamily(BsdAddressFamily family) + { + switch (family) + { + case BsdAddressFamily.Unspecified: + return AddressFamily.Unspecified; + case BsdAddressFamily.InterNetwork: + return AddressFamily.InterNetwork; + case BsdAddressFamily.InterNetworkV6: + return AddressFamily.InterNetworkV6; + case BsdAddressFamily.Unknown: + return AddressFamily.Unknown; + default: + throw new NotImplementedException(family.ToString()); + } + } + + private LinuxError SetResultErrno(IFileDescriptor socket, int result) + { + return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private ResultCode SocketInternal(ServiceCtx context, bool exempt) + { + BsdAddressFamily domain = (BsdAddressFamily)context.RequestData.ReadInt32(); + BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + + BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift); + type &= BsdSocketType.TypeMask; + + if (domain == BsdAddressFamily.Unknown) + { + return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((type == BsdSocketType.Seqpacket || type == BsdSocketType.Raw) && !_isPrivileged) + { + if (domain != BsdAddressFamily.InterNetwork || type != BsdSocketType.Raw || protocol != ProtocolType.Icmp) + { + return WriteBsdResult(context, -1, LinuxError.ENOENT); + } + } + + AddressFamily netDomain = ConvertBsdAddressFamily(domain); + + if (protocol == ProtocolType.IP) + { + if (type == BsdSocketType.Stream) + { + protocol = ProtocolType.Tcp; + } + else if (type == BsdSocketType.Dgram) + { + protocol = ProtocolType.Udp; + } + } + + ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol); + newBsdSocket.Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newBsdSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + if (exempt) + { + newBsdSocket.Disconnect(); + } + + return WriteBsdResult(context, newSockFd, errno); + } + + private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket socket, bool isRemote) + { + IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint; + + context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + + [CommandCmif(0)] + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject<copy, transfer_memory>, pid) -> u32 bsd_errno + public ResultCode RegisterClient(ServiceCtx context) + { + _context = BsdContext.GetOrRegister(context.Request.HandleDesc.PId); + + /* + typedef struct { + u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). + u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). + } BsdBufferConfig; + */ + + // bsd_error + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // StartMonitoring(u64, pid) + public ResultCode StartMonitoring(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode Socket(ServiceCtx context) + { + return SocketInternal(context, false); + } + + [CommandCmif(3)] + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode SocketExempt(ServiceCtx context) + { + return SocketInternal(context, true); + } + + [CommandCmif(4)] + // Open(u32 flags, array<unknown, 0x21> path) -> (i32 ret, u32 bsd_errno) + public ResultCode Open(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + int flags = context.RequestData.ReadInt32(); + + byte[] rawPath = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, rawPath); + + string path = Encoding.ASCII.GetString(rawPath); + + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { path, flags }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Select(u32 nfds, nn::socket::timeval timeout, buffer<nn::socket::fd_set, 0x21, 0> readfds_in, buffer<nn::socket::fd_set, 0x21, 0> writefds_in, buffer<nn::socket::fd_set, 0x21, 0> errorfds_in) + // -> (i32 ret, u32 bsd_errno, buffer<nn::socket::fd_set, 0x22, 0> readfds_out, buffer<nn::socket::fd_set, 0x22, 0> writefds_out, buffer<nn::socket::fd_set, 0x22, 0> errorfds_out) + public ResultCode Select(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (ulong readFdsInBufferPosition, ulong readFdsInBufferSize) = context.Request.GetBufferType0x21(0); + (ulong writeFdsInBufferPosition, ulong writeFdsInBufferSize) = context.Request.GetBufferType0x21(1); + (ulong errorFdsInBufferPosition, ulong errorFdsInBufferSize) = context.Request.GetBufferType0x21(2); + + (ulong readFdsOutBufferPosition, ulong readFdsOutBufferSize) = context.Request.GetBufferType0x22(0); + (ulong writeFdsOutBufferPosition, ulong writeFdsOutBufferSize) = context.Request.GetBufferType0x22(1); + (ulong errorFdsOutBufferPosition, ulong errorFdsOutBufferSize) = context.Request.GetBufferType0x22(2); + + List<IFileDescriptor> readFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(readFdsInBufferPosition, (int)readFdsInBufferSize)); + List<IFileDescriptor> writeFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(writeFdsInBufferPosition, (int)writeFdsInBufferSize)); + List<IFileDescriptor> errorFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(errorFdsInBufferPosition, (int)errorFdsInBufferSize)); + + int actualFdsCount = readFds.Count + writeFds.Count + errorFds.Count; + + if (fdsCount == 0 || actualFdsCount == 0) + { + WriteBsdResult(context, 0); + + return ResultCode.Success; + } + + PollEvent[] events = new PollEvent[actualFdsCount]; + + int index = 0; + + foreach (IFileDescriptor fd in readFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Input }, fd); + + index++; + } + + foreach (IFileDescriptor fd in writeFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Output }, fd); + + index++; + } + + foreach (IFileDescriptor fd in errorFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Error }, fd); + + index++; + } + + List<PollEvent>[] eventsByPollManager = new List<PollEvent>[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List<PollEvent>(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + } + } + } + + int updatedCount = 0; + + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Count > 0) + { + _pollManagers[i].Select(eventsByPollManager[i], timeout, out int updatedPollCount); + updatedCount += updatedPollCount; + } + } + + readFds.Clear(); + writeFds.Clear(); + errorFds.Clear(); + + foreach (PollEvent pollEvent in events) + { + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Contains(pollEvent)) + { + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Input)) + { + readFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorFds.Add(pollEvent.FileDescriptor); + } + } + } + } + + using var readFdsOut = context.Memory.GetWritableRegion(readFdsOutBufferPosition, (int)readFdsOutBufferSize); + using var writeFdsOut = context.Memory.GetWritableRegion(writeFdsOutBufferPosition, (int)writeFdsOutBufferSize); + using var errorFdsOut = context.Memory.GetWritableRegion(errorFdsOutBufferPosition, (int)errorFdsOutBufferSize); + + _context.BuildMask(readFds, readFdsOut.Memory.Span); + _context.BuildMask(writeFds, writeFdsOut.Memory.Span); + _context.BuildMask(errorFds, errorFdsOut.Memory.Span); + + WriteBsdResult(context, updatedCount); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // Poll(u32 nfds, u32 timeout, buffer<unknown, 0x21, 0> fds) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>) + public ResultCode Poll(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + + if (timeout < -1 || fdsCount < 0 || (ulong)(fdsCount * 8) > inputBufferSize) + { + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + + PollEvent[] events = new PollEvent[fdsCount]; + + for (int i = 0; i < fdsCount; i++) + { + PollEventData pollEventData = context.Memory.Read<PollEventData>(inputBufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>())); + + IFileDescriptor fileDescriptor = _context.RetrieveFileDescriptor(pollEventData.SocketFd); + + if (fileDescriptor == null) + { + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + + events[i] = new PollEvent(pollEventData, fileDescriptor); + } + + List<PollEvent> discoveredEvents = new List<PollEvent>(); + List<PollEvent>[] eventsByPollManager = new List<PollEvent>[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List<PollEvent>(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + discoveredEvents.Add(evnt); + } + } + } + + foreach (PollEvent evnt in events) + { + if (!discoveredEvents.Contains(evnt)) + { + Logger.Error?.Print(LogClass.ServiceBsd, $"Poll operation is not supported for {evnt.FileDescriptor.GetType().Name}!"); + + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + } + + int updateCount = 0; + + LinuxError errno = LinuxError.SUCCESS; + + if (fdsCount != 0) + { + bool IsUnexpectedLinuxError(LinuxError error) + { + return error != LinuxError.SUCCESS && error != LinuxError.ETIMEDOUT; + } + + // Hybrid approach + long budgetLeftMilliseconds; + + if (timeout == -1) + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + uint.MaxValue; + } + else + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + timeout; + } + + do + { + for (int i = 0; i < eventsByPollManager.Length; i++) + { + if (eventsByPollManager[i].Count == 0) + { + continue; + } + + errno = _pollManagers[i].Poll(eventsByPollManager[i], 0, out updateCount); + + if (IsUnexpectedLinuxError(errno)) + { + break; + } + + if (updateCount > 0) + { + break; + } + } + + if (updateCount > 0) + { + break; + } + + // If we are here, that mean nothing was available, sleep for 50ms + context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + } + while (PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); + } + else if (timeout == -1) + { + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProcess. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + context.Device.System.KernelContext.Syscall.SleepThread(timeout); + } + + // TODO: Spanify + for (int i = 0; i < fdsCount; i++) + { + context.Memory.Write(outputBufferPosition + (ulong)(i * Unsafe.SizeOf<PollEventData>()), events[i].Data); + } + + // In case of non blocking call timeout should not be returned. + if (timeout == 0 && errno == LinuxError.ETIMEDOUT) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, updateCount, errno); + } + + [CommandCmif(7)] + // Sysctl(buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>) + public ResultCode Sysctl(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array<i8, 0x22> message) + public ResultCode Recv(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Receive(out result, receiveRegion.Memory.Span, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(9)] + // RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<i8, 0x22, 0> message, buffer<nn::socket::sockaddr_in, 0x22, 0x10>) + public ResultCode RecvFrom(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(0); + (ulong sockAddrOutPosition, ulong sockAddrOutSize) = context.Request.GetBufferType0x22(1); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.ReceiveFrom(out result, receiveRegion.Memory.Span, receiveRegion.Memory.Span.Length, socketFlags, out IPEndPoint endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + + if (sockAddrOutSize != 0 && sockAddrOutSize >= (ulong) Unsafe.SizeOf<BsdSockAddr>()) + { + context.Memory.Write(sockAddrOutPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + else + { + errno = LinuxError.ENOMEM; + } + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(10)] + // Send(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno) + public ResultCode Send(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Send(out result, sendBuffer, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(11)] + // SendTo(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno) + public ResultCode SendTo(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(0); + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(1); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.SendTo(out result, sendBuffer, sendBuffer.Length, socketFlags, endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(12)] + // Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode Accept(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Accept(out ISocket newSocket); + + if (newSocket == null && errno == LinuxError.SUCCESS) + { + errno = LinuxError.EWOULDBLOCK; + } + else if (errno == LinuxError.SUCCESS) + { + int newSockFd = _context.RegisterFileDescriptor(newSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + else + { + WriteSockAddr(context, bufferPos, newSocket, true); + } + + WriteBsdResult(context, newSockFd, errno); + + context.ResponseData.Write(0x10); + + return ResultCode.Success; + } + } + + return WriteBsdResult(context, -1, errno); + } + + [CommandCmif(13)] + // Bind(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10> addr) -> (i32 ret, u32 bsd_errno) + public ResultCode Bind(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.Bind(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(14)] + // Connect(u32 socket, buffer<nn::socket::sockaddr_in, 0x21, 0x10>) -> (i32 ret, u32 bsd_errno) + public ResultCode Connect(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read<BsdSockAddr>(bufferPosition).ToIPEndPoint(); + + errno = socket.Connect(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(15)] + // GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode GetPeerName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + if (socket != null) + { + errno = LinuxError.ENOTCONN; + + if (socket.RemoteEndPoint != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPosition, socket, true); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>()); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(16)] + // GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<nn::socket::sockaddr_in, 0x22, 0x10> addr) + public ResultCode GetSockName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, false); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf<BsdSockAddr>()); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(17)] + // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer<unknown, 0x22, 0>) + public ResultCode GetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + WritableRegion optionValue = context.Memory.GetWritableRegion(bufferPosition, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.GetSocketOption(option, level, optionValue.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + optionValue.Dispose(); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(18)] + // Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public ResultCode Listen(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int backlog = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Listen(backlog); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(19)] + // Ioctl(u32 fd, u32 request, u32 bufcount, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>, buffer<unknown, 0x22, 0>) + public ResultCode Ioctl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdIoctl cmd = (BsdIoctl)context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + switch (cmd) + { + case BsdIoctl.AtMark: + errno = LinuxError.SUCCESS; + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + + // FIXME: OOB not implemented. + context.Memory.Write(bufferPosition, 0); + break; + + default: + errno = LinuxError.EOPNOTSUPP; + + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}"); + break; + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(20)] + // Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno) + public ResultCode Fcntl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int cmd = context.RequestData.ReadInt32(); + int arg = context.RequestData.ReadInt32(); + + int result = 0; + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + if (cmd == 0x3) + { + result = !socket.Blocking ? 0x800 : 0; + } + else if (cmd == 0x4 && arg == 0x800) + { + socket.Blocking = false; + result = 0; + } + else + { + errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(21)] + // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0> option_value) -> (i32 ret, u32 bsd_errno) + public ResultCode SetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> optionValue = context.Memory.GetSpan(bufferPos, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.SetSocketOption(option, level, optionValue); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(22)] + // Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode Shutdown(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = socket.Shutdown((BsdSocketShutdownFlags)how); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(23)] + // ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode ShutdownAllSockets(ServiceCtx context) + { + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = _context.ShutdownAllSockets((BsdSocketShutdownFlags)how); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(24)] + // Write(u32 fd, buffer<i8, 0x21, 0> message) -> (i32 ret, u32 bsd_errno) + public ResultCode Write(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan<byte> sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Write(out result, sendBuffer); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(25)] + // Read(u32 fd) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message) + public ResultCode Read(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Read(out result, receiveRegion.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(26)] + // Close(u32 fd) -> (i32 ret, u32 bsd_errno) + public ResultCode Close(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + + if (_context.CloseFileDescriptor(fd)) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(27)] + // DuplicateSocket(u32 fd, u64 reserved) -> (i32 ret, u32 bsd_errno) + public ResultCode DuplicateSocket(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + ulong reserved = context.RequestData.ReadUInt64(); + + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; + + if (_isPrivileged) + { + errno = LinuxError.SUCCESS; + + newSockFd = _context.DuplicateFileDescriptor(fd); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + } + + return WriteBsdResult(context, newSockFd, errno); + } + + + [CommandCmif(29)] // 7.0.0+ + // RecvMMsg(u32 fd, u32 vlen, u32 flags, u32 reserved, nn::socket::TimeVal timeout) -> (i32 ret, u32 bsd_errno, buffer<bytes, 6> message); + public ResultCode RecvMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + uint reserved = context.RequestData.ReadUInt32(); + TimeVal timeout = context.RequestData.ReadStruct<TimeVal>(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.RecvMMsg(out result, message, socketFlags, timeout); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(30)] // 7.0.0+ + // SendMMsg(u32 fd, u32 vlen, u32 flags) -> (i32 ret, u32 bsd_errno, buffer<bytes, 6> message); + public ResultCode SendMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.SendMMsg(out result, message, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(31)] // 7.0.0+ + // EventFd(nn::socket::EventFdFlags flags, u64 initval) -> (i32 ret, u32 bsd_errno) + public ResultCode EventFd(ServiceCtx context) + { + EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + ulong initialValue = context.RequestData.ReadUInt64(); + + EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newEventFile); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + return WriteBsdResult(context, newSockFd, errno); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs new file mode 100644 index 00000000..9d4f81ce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface IFileDescriptor : IDisposable + { + bool Blocking { get; set; } + int Refcount { get; set; } + + LinuxError Read(out int readSize, Span<byte> buffer); + + LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs new file mode 100644 index 00000000..05874868 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs @@ -0,0 +1,53 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface ISocket : IDisposable, IFileDescriptor + { + IPEndPoint RemoteEndPoint { get; } + IPEndPoint LocalEndPoint { get; } + + AddressFamily AddressFamily { get; } + + SocketType SocketType { get; } + + ProtocolType ProtocolType { get; } + + IntPtr Handle { get; } + + LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags); + + LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint); + + LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags); + + LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint); + + LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout); + + LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags); + + LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue); + + LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue); + + bool Poll(int microSeconds, SelectMode mode); + + LinuxError Bind(IPEndPoint localEndPoint); + + LinuxError Connect(IPEndPoint remoteEndPoint); + + LinuxError Listen(int backlog); + + LinuxError Accept(out ISocket newSocket); + + void Disconnect(); + + LinuxError Shutdown(BsdSocketShutdownFlags how); + + void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs new file mode 100644 index 00000000..6514d485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs @@ -0,0 +1,153 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptor : IFileDescriptor + { + private ulong _value; + private readonly EventFdFlags _flags; + + private object _lock = new object(); + + public bool Blocking { get => !_flags.HasFlag(EventFdFlags.NonBlocking); set => throw new NotSupportedException(); } + + public ManualResetEvent WriteEvent { get; } + public ManualResetEvent ReadEvent { get; } + + public EventFileDescriptor(ulong value, EventFdFlags flags) + { + // FIXME: We should support blocking operations. + // Right now they can't be supported because it would cause the + // service to lock up as we only have one thread processing requests. + flags |= EventFdFlags.NonBlocking; + + _value = value; + _flags = flags; + + WriteEvent = new ManualResetEvent(false); + ReadEvent = new ManualResetEvent(false); + UpdateEventStates(); + } + + public int Refcount { get; set; } + + public void Dispose() + { + WriteEvent.Dispose(); + ReadEvent.Dispose(); + } + + private void ResetEventStates() + { + WriteEvent.Reset(); + ReadEvent.Reset(); + } + + private void UpdateEventStates() + { + if (_value > 0) + { + ReadEvent.Set(); + } + + if (_value != uint.MaxValue - 1) + { + WriteEvent.Set(); + } + } + + public LinuxError Read(out int readSize, Span<byte> buffer) + { + if (buffer.Length < sizeof(ulong)) + { + readSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0]; + + if (_value == 0) + { + if (Blocking) + { + while (_value == 0) + { + Monitor.Wait(_lock); + } + } + else + { + readSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + readSize = sizeof(ulong); + + if (_flags.HasFlag(EventFdFlags.Semaphore)) + { + --_value; + + count = 1; + } + else + { + count = _value; + + _value = 0; + } + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + + public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer) + { + if (!MemoryMarshal.TryRead(buffer, out ulong count) || count == ulong.MaxValue) + { + writeSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + if (_value > _value + count) + { + if (Blocking) + { + Monitor.Wait(_lock); + } + else + { + writeSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + writeSize = sizeof(ulong); + + _value += count; + Monitor.Pulse(_lock); + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs new file mode 100644 index 00000000..e0ab68c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs @@ -0,0 +1,122 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptorPollManager : IPollManager + { + private static EventFileDescriptorPollManager _instance; + + public static EventFileDescriptorPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new EventFileDescriptorPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is EventFileDescriptor; + } + + public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount) + { + updatedCount = 0; + + List<ManualResetEvent> waiters = new List<ManualResetEvent>(); + + for (int i = 0; i < events.Count; i++) + { + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + bool isValidEvent = false; + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || + evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + waiters.Add(socket.ReadEvent); + + isValidEvent = true; + } + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + waiters.Add(socket.WriteEvent); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + + return LinuxError.EINVAL; + } + } + + int index = WaitHandle.WaitAny(waiters.ToArray(), timeoutMilliseconds); + + if (index != WaitHandle.WaitTimeout) + { + for (int i = 0; i < events.Count; i++) + { + PollEventTypeMask outputEvents = 0; + + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + if (socket.ReadEvent.WaitOne(0)) + { + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + outputEvents |= PollEventTypeMask.Input; + } + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + outputEvents |= PollEventTypeMask.UrgentInput; + } + } + + if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + && socket.WriteEvent.WaitOne(0)) + { + outputEvents |= PollEventTypeMask.Output; + } + + + if (outputEvents != 0) + { + evnt.Data.OutputEvents = outputEvents; + + updatedCount++; + } + } + } + else + { + return LinuxError.ETIMEDOUT; + } + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount) + { + // TODO: Implement Select for event file descriptors + updatedCount = 0; + + return LinuxError.EOPNOTSUPP; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs new file mode 100644 index 00000000..75efc49a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs @@ -0,0 +1,530 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocket : ISocket + { + public int Refcount { get; set; } + + public AddressFamily AddressFamily => Socket.AddressFamily; + + public SocketType SocketType => Socket.SocketType; + + public ProtocolType ProtocolType => Socket.ProtocolType; + + public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } + + public IntPtr Handle => Socket.Handle; + + public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; + + public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint; + + public Socket Socket { get; } + + public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + Socket = new Socket(addressFamily, socketType, protocolType); + Refcount = 1; + } + + private ManagedSocket(Socket socket) + { + Socket = socket; + Refcount = 1; + } + + private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) + { + SocketFlags socketFlags = SocketFlags.None; + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) + { + socketFlags |= SocketFlags.OutOfBand; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek)) + { + socketFlags |= SocketFlags.Peek; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute)) + { + socketFlags |= SocketFlags.DontRoute; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc)) + { + socketFlags |= SocketFlags.Truncated; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc)) + { + socketFlags |= SocketFlags.ControlDataTruncated; + } + + bsdSocketFlags &= ~(BsdSocketFlags.Oob | + BsdSocketFlags.Peek | + BsdSocketFlags.DontRoute | + BsdSocketFlags.DontWait | + BsdSocketFlags.Trunc | + BsdSocketFlags.CTrunc); + + if (bsdSocketFlags != BsdSocketFlags.None) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}"); + } + + return socketFlags; + } + + public LinuxError Accept(out ISocket newSocket) + { + try + { + newSocket = new ManagedSocket(Socket.Accept()); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + newSocket = null; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Bind(IPEndPoint localEndPoint) + { + try + { + Socket.Bind(localEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public void Close() + { + Socket.Close(); + } + + public LinuxError Connect(IPEndPoint remoteEndPoint) + { + try + { + Socket.Connect(remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK) + { + return LinuxError.EINPROGRESS; + } + else + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } + + public void Disconnect() + { + Socket.Disconnect(true); + } + + public void Dispose() + { + Socket.Close(); + Socket.Dispose(); + } + + public LinuxError Listen(int backlog) + { + try + { + Socket.Listen(backlog); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public bool Poll(int microSeconds, SelectMode mode) + { + return Socket.Poll(microSeconds, mode); + } + + public LinuxError Shutdown(BsdSocketShutdownFlags how) + { + try + { + Socket.Shutdown((SocketShutdown)how); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags) + { + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags)); + + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint) + { + remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + EndPoint temp = new IPEndPoint(IPAddress.Any, 0); + + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + if (!Socket.IsBound) + { + receiveSize = -1; + + return LinuxError.EOPNOTSUPP; + } + + receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp); + + remoteEndPoint = (IPEndPoint)temp; + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError Send(out int sendSize, ReadOnlySpan<byte> buffer, BsdSocketFlags flags) + { + try + { + sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags)); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendTo(out int sendSize, ReadOnlySpan<byte> buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint) + { + try + { + sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span<byte> optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + byte[] tempOptionValue = new byte[optionValue.Length]; + + Socket.GetSocketOption(level, optionName, tempOptionValue); + + tempOptionValue.AsSpan().CopyTo(optionValue); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan<byte> optionValue) + { + try + { + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); + + return LinuxError.EOPNOTSUPP; + } + + int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue); + + if (level == SocketOptionLevel.Socket && option == BsdSocketOption.SoLinger) + { + int value2 = 0; + + if (optionValue.Length >= 8) + { + value2 = MemoryMarshal.Read<int>(optionValue[4..]); + } + + Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); + } + else + { + Socket.SetSocketOption(level, optionName, value); + } + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Read(out int readSize, Span<byte> buffer) + { + return Receive(out readSize, buffer, BsdSocketFlags.None); + } + + public LinuxError Write(out int writeSize, ReadOnlySpan<byte> buffer) + { + return Send(out writeSize, buffer, BsdSocketFlags.None); + } + + private bool CanSupportMMsgHdr(BsdMMsgHdr message) + { + for (int i = 0; i < message.Messages.Length; i++) + { + if (message.Messages[i].Name != null || + message.Messages[i].Control != null) + { + return false; + } + } + + return true; + } + + private static IList<ArraySegment<byte>> ConvertMessagesToBuffer(BsdMMsgHdr message) + { + int segmentCount = 0; + int index = 0; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + segmentCount += msgHeader.Iov.Length; + } + + ArraySegment<byte>[] buffers = new ArraySegment<byte>[segmentCount]; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + foreach (byte[] iov in msgHeader.Iov) + { + buffers[index++] = new ArraySegment<byte>(iov); + } + + // Clear the length + msgHeader.Length = 0; + } + + return buffers; + } + + private static void UpdateMessages(out int vlen, BsdMMsgHdr message, int transferedSize) + { + int bytesLeft = transferedSize; + int index = 0; + + while (bytesLeft > 0) + { + // First ensure we haven't finished all buffers + if (index >= message.Messages.Length) + { + break; + } + + BsdMsgHdr msgHeader = message.Messages[index]; + + int possiblyTransferedBytes = 0; + + foreach (byte[] iov in msgHeader.Iov) + { + possiblyTransferedBytes += iov.Length; + } + + int storedBytes; + + if (bytesLeft > possiblyTransferedBytes) + { + storedBytes = possiblyTransferedBytes; + index++; + } + else + { + storedBytes = bytesLeft; + } + + msgHeader.Length = (uint)storedBytes; + bytesLeft -= storedBytes; + } + + Debug.Assert(bytesLeft == 0); + + vlen = index + 1; + } + + // TODO: Find a way to support passing the timeout somehow without changing the socket ReceiveTimeout. + public LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (receiveSize > 0) + { + UpdateMessages(out vlen, message, receiveSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int sendSize = Socket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (sendSize > 0) + { + UpdateMessages(out vlen, message, sendSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs new file mode 100644 index 00000000..1b305dfb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs @@ -0,0 +1,177 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocketPollManager : IPollManager + { + private static ManagedSocketPollManager _instance; + + public static ManagedSocketPollManager Instance + { + get + { + if (_instance == null) + { + _instance = new ManagedSocketPollManager(); + } + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is ManagedSocket; + } + + public LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount) + { + List<Socket> readEvents = new List<Socket>(); + List<Socket> writeEvents = new List<Socket>(); + List<Socket> errorEvents = new List<Socket>(); + + updatedCount = 0; + + foreach (PollEvent evnt in events) + { + ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor; + + bool isValidEvent = evnt.Data.InputEvents == 0; + + errorEvents.Add(socket.Socket); + + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0) + { + writeEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + return LinuxError.EINVAL; + } + } + + try + { + int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000; + + Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + foreach (PollEvent evnt in events) + { + Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket; + + PollEventTypeMask outputEvents = evnt.Data.OutputEvents & ~evnt.Data.InputEvents; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + outputEvents |= PollEventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Output; + } + + evnt.Data.OutputEvents = outputEvents; + } + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount) + { + List<Socket> readEvents = new(); + List<Socket> writeEvents = new(); + List<Socket> errorEvents = new(); + + updatedCount = 0; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + readEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorEvents.Add(socket.Socket); + } + } + + Socket.Select(readEvents, writeEvents, errorEvents, timeout); + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (readEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Input; + } + + if (writeEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Output; + } + + if (errorEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Error; + } + } + + return LinuxError.SUCCESS; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs new file mode 100644 index 00000000..0f24a57f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs @@ -0,0 +1,134 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum WsaError + { + /* + * All Windows Sockets error constants are biased by WSABASEERR from + * the "normal" + */ + WSABASEERR = 10000, + + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR + 4), + WSAEBADF = (WSABASEERR + 9), + WSAEACCES = (WSABASEERR + 13), + WSAEFAULT = (WSABASEERR + 14), + WSAEINVAL = (WSABASEERR + 22), + WSAEMFILE = (WSABASEERR + 24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR + 35), + WSAEINPROGRESS = (WSABASEERR + 36), + WSAEALREADY = (WSABASEERR + 37), + WSAENOTSOCK = (WSABASEERR + 38), + WSAEDESTADDRREQ = (WSABASEERR + 39), + WSAEMSGSIZE = (WSABASEERR + 40), + WSAEPROTOTYPE = (WSABASEERR + 41), + WSAENOPROTOOPT = (WSABASEERR + 42), + WSAEPROTONOSUPPORT = (WSABASEERR + 43), + WSAESOCKTNOSUPPORT = (WSABASEERR + 44), + WSAEOPNOTSUPP = (WSABASEERR + 45), + WSAEPFNOSUPPORT = (WSABASEERR + 46), + WSAEAFNOSUPPORT = (WSABASEERR + 47), + WSAEADDRINUSE = (WSABASEERR + 48), + WSAEADDRNOTAVAIL = (WSABASEERR + 49), + WSAENETDOWN = (WSABASEERR + 50), + WSAENETUNREACH = (WSABASEERR + 51), + WSAENETRESET = (WSABASEERR + 52), + WSAECONNABORTED = (WSABASEERR + 53), + WSAECONNRESET = (WSABASEERR + 54), + WSAENOBUFS = (WSABASEERR + 55), + WSAEISCONN = (WSABASEERR + 56), + WSAENOTCONN = (WSABASEERR + 57), + WSAESHUTDOWN = (WSABASEERR + 58), + WSAETOOMANYREFS = (WSABASEERR + 59), + WSAETIMEDOUT = (WSABASEERR + 60), + WSAECONNREFUSED = (WSABASEERR + 61), + WSAELOOP = (WSABASEERR + 62), + WSAENAMETOOLONG = (WSABASEERR + 63), + WSAEHOSTDOWN = (WSABASEERR + 64), + WSAEHOSTUNREACH = (WSABASEERR + 65), + WSAENOTEMPTY = (WSABASEERR + 66), + WSAEPROCLIM = (WSABASEERR + 67), + WSAEUSERS = (WSABASEERR + 68), + WSAEDQUOT = (WSABASEERR + 69), + WSAESTALE = (WSABASEERR + 70), + WSAEREMOTE = (WSABASEERR + 71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR + 91), + WSAVERNOTSUPPORTED = (WSABASEERR + 92), + WSANOTINITIALISED = (WSABASEERR + 93), + WSAEDISCON = (WSABASEERR + 101), + WSAENOMORE = (WSABASEERR + 102), + WSAECANCELLED = (WSABASEERR + 103), + WSAEINVALIDPROCTABLE = (WSABASEERR + 104), + WSAEINVALIDPROVIDER = (WSABASEERR + 105), + WSAEPROVIDERFAILEDINIT = (WSABASEERR + 106), + WSASYSCALLFAILURE = (WSABASEERR + 107), + WSASERVICE_NOT_FOUND = (WSABASEERR + 108), + WSATYPE_NOT_FOUND = (WSABASEERR + 109), + WSA_E_NO_MORE = (WSABASEERR + 110), + WSA_E_CANCELLED = (WSABASEERR + 111), + WSAEREFUSED = (WSABASEERR + 112), + + /* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR + 1001), + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR + 1002), + + /* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR + 1003), + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR + 1004), + + /* + * Define QOS related error return codes + * + */ + WSA_QOS_RECEIVERS = (WSABASEERR + 1005), + /* at least one Reserve has arrived */ + WSA_QOS_SENDERS = (WSABASEERR + 1006), + /* at least one Path has arrived */ + WSA_QOS_NO_SENDERS = (WSABASEERR + 1007), + /* there are no senders */ + WSA_QOS_NO_RECEIVERS = (WSABASEERR + 1008), + /* there are no receivers */ + WSA_QOS_REQUEST_CONFIRMED = (WSABASEERR + 1009), + /* Reserve has been confirmed */ + WSA_QOS_ADMISSION_FAILURE = (WSABASEERR + 1010), + /* error due to lack of resources */ + WSA_QOS_POLICY_FAILURE = (WSABASEERR + 1011), + /* rejected for administrative reasons - bad credentials */ + WSA_QOS_BAD_STYLE = (WSABASEERR + 1012), + /* unknown or conflicting style */ + WSA_QOS_BAD_OBJECT = (WSABASEERR + 1013), + /* problem with some part of the filterspec or providerspecific + * buffer in general */ + WSA_QOS_TRAFFIC_CTRL_ERROR = (WSABASEERR + 1014), + /* problem with some part of the flowspec */ + WSA_QOS_GENERIC_ERROR = (WSABASEERR + 1015) + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs new file mode 100644 index 00000000..5668d30b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -0,0 +1,225 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + static class WinSockHelper + { + private static readonly Dictionary<WsaError, LinuxError> _errorMap = new() + { + // WSAEINTR + { WsaError.WSAEINTR, LinuxError.EINTR }, + // WSAEWOULDBLOCK + { WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK }, + // WSAEINPROGRESS + { WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS }, + // WSAEALREADY + { WsaError.WSAEALREADY, LinuxError.EALREADY }, + // WSAENOTSOCK + { WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK }, + // WSAEDESTADDRREQ + { WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ }, + // WSAEMSGSIZE + { WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE }, + // WSAEPROTOTYPE + { WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE }, + // WSAENOPROTOOPT + { WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT }, + // WSAEPROTONOSUPPORT + { WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT }, + // WSAESOCKTNOSUPPORT + { WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT }, + // WSAEOPNOTSUPP + { WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP }, + // WSAEPFNOSUPPORT + { WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT }, + // WSAEAFNOSUPPORT + { WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT }, + // WSAEADDRINUSE + { WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE }, + // WSAEADDRNOTAVAIL + { WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL }, + // WSAENETDOWN + { WsaError.WSAENETDOWN, LinuxError.ENETDOWN }, + // WSAENETUNREACH + { WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH }, + // WSAENETRESET + { WsaError.WSAENETRESET, LinuxError.ENETRESET }, + // WSAECONNABORTED + { WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED }, + // WSAECONNRESET + { WsaError.WSAECONNRESET, LinuxError.ECONNRESET }, + // WSAENOBUFS + { WsaError.WSAENOBUFS, LinuxError.ENOBUFS }, + // WSAEISCONN + { WsaError.WSAEISCONN, LinuxError.EISCONN }, + // WSAENOTCONN + { WsaError.WSAENOTCONN, LinuxError.ENOTCONN }, + // WSAESHUTDOWN + { WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN }, + // WSAETOOMANYREFS + { WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS }, + // WSAETIMEDOUT + { WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT }, + // WSAECONNREFUSED + { WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED }, + // WSAELOOP + { WsaError.WSAELOOP, LinuxError.ELOOP }, + // WSAENAMETOOLONG + { WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG }, + // WSAEHOSTDOWN + { WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN }, + // WSAEHOSTUNREACH + { WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH }, + // WSAENOTEMPTY + { WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY }, + // WSAEUSERS + { WsaError.WSAEUSERS, LinuxError.EUSERS }, + // WSAEDQUOT + { WsaError.WSAEDQUOT, LinuxError.EDQUOT }, + // WSAESTALE + { WsaError.WSAESTALE, LinuxError.ESTALE }, + // WSAEREMOTE + { WsaError.WSAEREMOTE, LinuxError.EREMOTE }, + // WSAEINVAL + { WsaError.WSAEINVAL, LinuxError.EINVAL }, + // WSAEFAULT + { WsaError.WSAEFAULT, LinuxError.EFAULT }, + // NOERROR + { 0, 0 } + }; + + private static readonly Dictionary<int, LinuxError> _errorMapMacOs = new() + { + { 35, LinuxError.EAGAIN }, + { 11, LinuxError.EDEADLOCK }, + { 91, LinuxError.ENOMSG }, + { 90, LinuxError.EIDRM }, + { 77, LinuxError.ENOLCK }, + { 70, LinuxError.ESTALE }, + { 36, LinuxError.EINPROGRESS }, + { 37, LinuxError.EALREADY }, + { 38, LinuxError.ENOTSOCK }, + { 39, LinuxError.EDESTADDRREQ }, + { 40, LinuxError.EMSGSIZE }, + { 41, LinuxError.EPROTOTYPE }, + { 42, LinuxError.ENOPROTOOPT }, + { 43, LinuxError.EPROTONOSUPPORT }, + { 44, LinuxError.ESOCKTNOSUPPORT }, + { 45, LinuxError.EOPNOTSUPP }, + { 46, LinuxError.EPFNOSUPPORT }, + { 47, LinuxError.EAFNOSUPPORT }, + { 48, LinuxError.EADDRINUSE }, + { 49, LinuxError.EADDRNOTAVAIL }, + { 50, LinuxError.ENETDOWN }, + { 51, LinuxError.ENETUNREACH }, + { 52, LinuxError.ENETRESET }, + { 53, LinuxError.ECONNABORTED }, + { 54, LinuxError.ECONNRESET }, + { 55, LinuxError.ENOBUFS }, + { 56, LinuxError.EISCONN }, + { 57, LinuxError.ENOTCONN }, + { 58, LinuxError.ESHUTDOWN }, + { 60, LinuxError.ETIMEDOUT }, + { 61, LinuxError.ECONNREFUSED }, + { 64, LinuxError.EHOSTDOWN }, + { 65, LinuxError.EHOSTUNREACH }, + { 68, LinuxError.EUSERS }, + { 62, LinuxError.ELOOP }, + { 63, LinuxError.ENAMETOOLONG }, + { 66, LinuxError.ENOTEMPTY }, + { 69, LinuxError.EDQUOT }, + { 71, LinuxError.EREMOTE }, + { 78, LinuxError.ENOSYS }, + { 59, LinuxError.ETOOMANYREFS }, + { 92, LinuxError.EILSEQ }, + { 89, LinuxError.ECANCELED }, + { 84, LinuxError.EOVERFLOW } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _soSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, SocketOptionName.Debug }, + { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, + { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, + { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, + { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, + { BsdSocketOption.SoLinger, SocketOptionName.Linger }, + { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, + { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, + { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, + { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, + { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, + { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, + { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, + { BsdSocketOption.SoError, SocketOptionName.Error }, + { BsdSocketOption.SoType, SocketOptionName.Type } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _ipSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, + { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, + { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, + { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, + { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, + { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, + { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, + { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, + { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, + { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, + { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership } + }; + + private static readonly Dictionary<BsdSocketOption, SocketOptionName> _tcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, + { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, + { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, + { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount } + }; + + public static LinuxError ConvertError(WsaError errorCode) + { + if (OperatingSystem.IsMacOS()) + { + if (_errorMapMacOs.TryGetValue((int)errorCode, out LinuxError errno)) + { + return errno; + } + } + else + { + if (_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + return errno; + } + } + + return (LinuxError)errorCode; + } + + public static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) + { + var table = level switch + { + SocketOptionLevel.Socket => _soSocketOptionMap, + SocketOptionLevel.IP => _ipSocketOptionMap, + SocketOptionLevel.Tcp => _tcpSocketOptionMap, + _ => null + }; + + if (table == null) + { + name = default; + return false; + } + + return table.TryGetValue(option, out name); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs new file mode 100644 index 00000000..798fc015 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsdcfg")] + class ServerInterface : IpcService + { + public ServerInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs new file mode 100644 index 00000000..37461bb2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdAddressFamily : uint + { + Unspecified, + InterNetwork = 2, + InterNetworkV6 = 28, + + Unknown = uint.MaxValue + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs new file mode 100644 index 00000000..1dfa5a5f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdIoctl + { + AtMark = 0x40047307 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs new file mode 100644 index 00000000..f97b8f5b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMMsgHdr + { + public BsdMsgHdr[] Messages { get; } + + private BsdMMsgHdr(BsdMsgHdr[] messages) + { + Messages = messages; + } + + public static LinuxError Serialize(Span<byte> rawData, BsdMMsgHdr message) + { + rawData[0] = 0x8; + rawData = rawData[1..]; + + for (int index = 0; index < message.Messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Serialize(ref rawData, message.Messages[index]); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMMsgHdr message, ReadOnlySpan<byte> rawData, int vlen) + { + message = null; + + BsdMsgHdr[] messages = new BsdMsgHdr[vlen]; + + // Skip "header" byte (Nintendo also ignore it) + rawData = rawData[1..]; + + for (int index = 0; index < messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Deserialize(out messages[index], ref rawData); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + message = new BsdMMsgHdr(messages); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs new file mode 100644 index 00000000..07c97182 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs @@ -0,0 +1,212 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMsgHdr + { + public byte[] Name { get; } + public byte[][] Iov { get; } + public byte[] Control { get; } + public BsdSocketFlags Flags { get; } + public uint Length; + + private BsdMsgHdr(byte[] name, byte[][] iov, byte[] control, BsdSocketFlags flags, uint length) + { + Name = name; + Iov = iov; + Control = control; + Flags = flags; + Length = length; + } + + public static LinuxError Serialize(ref Span<byte> rawData, BsdMsgHdr message) + { + int msgNameLength = message.Name == null ? 0 : message.Name.Length; + int iovCount = message.Iov == null ? 0 : message.Iov.Length; + int controlLength = message.Control == null ? 0 : message.Control.Length; + BsdSocketFlags flags = message.Flags; + + if (!MemoryMarshal.TryWrite(rawData, ref msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + message.Name.CopyTo(rawData); + rawData = rawData[msgNameLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, ref iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + for (int index = 0; index < iovCount; index++) + { + ulong iovLength = (ulong)message.Iov[index].Length; + + if (!MemoryMarshal.TryWrite(rawData, ref iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + message.Iov[index].CopyTo(rawData); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryWrite(rawData, ref controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + message.Control.CopyTo(rawData); + rawData = rawData[controlLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, ref flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryWrite(rawData, ref message.Length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMsgHdr message, ref ReadOnlySpan<byte> rawData) + { + byte[] name = null; + byte[][] iov = null; + byte[] control = null; + + message = null; + + if (!MemoryMarshal.TryRead(rawData, out uint msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + name = rawData[..(int)msgNameLength].ToArray(); + rawData = rawData[(int)msgNameLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out uint iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + iov = new byte[iovCount][]; + + for (int index = 0; index < iov.Length; index++) + { + if (!MemoryMarshal.TryRead(rawData, out ulong iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + iov[index] = rawData[..(int)iovLength].ToArray(); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryRead(rawData, out uint controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + control = rawData[..(int)controlLength].ToArray(); + rawData = rawData[(int)controlLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out BsdSocketFlags flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryRead(rawData, out uint length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + message = new BsdMsgHdr(name, iov, control, flags, length); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs new file mode 100644 index 00000000..67c11e54 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct BsdSockAddr + { + public byte Length; + public byte Family; + public ushort Port; + public Array4<byte> Address; + private Array8<byte> _reserved; + + public IPEndPoint ToIPEndPoint() + { + IPAddress address = new IPAddress(Address.AsSpan()); + int port = (ushort)IPAddress.NetworkToHostOrder((short)Port); + + return new IPEndPoint(address, port); + } + + public static BsdSockAddr FromIPEndPoint(IPEndPoint endpoint) + { + BsdSockAddr result = new BsdSockAddr + { + Length = 0, + Family = (byte)endpoint.AddressFamily, + Port = (ushort)IPAddress.HostToNetworkOrder((short)endpoint.Port) + }; + + endpoint.Address.GetAddressBytes().AsSpan().CopyTo(result.Address.AsSpan()); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs new file mode 100644 index 00000000..be5991ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum BsdSocketCreationFlags + { + None = 0, + CloseOnExecution = 1, + NonBlocking = 2, + + FlagsShift = 28 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs new file mode 100644 index 00000000..4408c89a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketFlags + { + None = 0, + Oob = 0x1, + Peek = 0x2, + DontRoute = 0x4, + Eor = 0x8, + Trunc = 0x10, + CTrunc = 0x20, + WaitAll = 0x40, + DontWait = 0x80, + Eof = 0x100, + Notification = 0x2000, + Nbio = 0x4000, + Compat = 0x8000, + SoCallbck = 0x10000, + NoSignal = 0x20000, + CMsgCloExec = 0x40000 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs new file mode 100644 index 00000000..4d0d1dcf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs @@ -0,0 +1,119 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketOption + { + SoDebug = 0x1, + SoAcceptConn = 0x2, + SoReuseAddr = 0x4, + SoKeepAlive = 0x8, + SoDontRoute = 0x10, + SoBroadcast = 0x20, + SoUseLoopBack = 0x40, + SoLinger = 0x80, + SoOobInline = 0x100, + SoReusePort = 0x200, + SoTimestamp = 0x400, + SoNoSigpipe = 0x800, + SoAcceptFilter = 0x1000, + SoBinTime = 0x2000, + SoNoOffload = 0x4000, + SoNoDdp = 0x8000, + SoReusePortLb = 0x10000, + SoRError = 0x20000, + + SoSndBuf = 0x1001, + SoRcvBuf = 0x1002, + SoSndLoWat = 0x1003, + SoRcvLoWat = 0x1004, + SoSndTimeo = 0x1005, + SoRcvTimeo = 0x1006, + SoError = 0x1007, + SoType = 0x1008, + SoLabel = 0x1009, + SoPeerLabel = 0x1010, + SoListenQLimit = 0x1011, + SoListenQLen = 0x1012, + SoListenIncQLen = 0x1013, + SoSetFib = 0x1014, + SoUserCookie = 0x1015, + SoProtocol = 0x1016, + SoTsClock = 0x1017, + SoMaxPacingRate = 0x1018, + SoDomain = 0x1019, + + IpOptions = 1, + IpHdrIncl = 2, + IpTos = 3, + IpTtl = 4, + IpRecvOpts = 5, + IpRecvRetOpts = 6, + IpRecvDstAddr = 7, + IpRetOpts = 8, + IpMulticastIf = 9, + IpMulticastTtl = 10, + IpMulticastLoop = 11, + IpAddMembership = 12, + IpDropMembership = 13, + IpMulticastVif = 14, + IpRsvpOn = 15, + IpRsvpOff = 16, + IpRsvpVifOn = 17, + IpRsvpVifOff = 18, + IpPortRange = 19, + IpRecvIf = 20, + IpIpsecPolicy = 21, + IpOnesBcast = 23, + IpBindany = 24, + IpBindMulti = 25, + IpRssListenBucket = 26, + IpOrigDstAddr = 27, + + IpFwTableAdd = 40, + IpFwTableDel = 41, + IpFwTableFlush = 42, + IpFwTableGetSize = 43, + IpFwTableList = 44, + + IpFw3 = 48, + IpDummyNet3 = 49, + + IpFwAdd = 50, + IpFwDel = 51, + IpFwFlush = 52, + IpFwZero = 53, + IpFwGet = 54, + IpFwResetLog = 55, + + IpFwNatCfg = 56, + IpFwNatDel = 57, + IpFwNatGetConfig = 58, + IpFwNatGetLog = 59, + + IpDummyNetConfigure = 60, + IpDummyNetDel = 61, + IpDummyNetFlush = 62, + IpDummyNetGet = 64, + + IpRecvTtl = 65, + IpMinTtl = 66, + IpDontFrag = 67, + IpRecvTos = 68, + + IpAddSourceMembership = 70, + IpDropSourceMembership = 71, + IpBlockSource = 72, + IpUnblockSource = 73, + + TcpNoDelay = 1, + TcpMaxSeg = 2, + TcpNoPush = 4, + TcpNoOpt = 8, + TcpMd5Sig = 16, + TcpInfo = 32, + TcpCongestion = 64, + TcpKeepInit = 128, + TcpKeepIdle = 256, + TcpKeepIntvl = 512, + TcpKeepCnt = 1024 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs new file mode 100644 index 00000000..13230ac3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketShutdownFlags + { + Receive, + Send, + ReceiveAndSend + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs new file mode 100644 index 00000000..b54c7886 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketType + { + Stream = 1, + Dgram, + Raw, + Rdm, + Seqpacket, + + TypeMask = 0xFFFFFFF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs new file mode 100644 index 00000000..e01d8226 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum EventFdFlags : uint + { + None = 0, + Semaphore = 1 << 0, + NonBlocking = 1 << 2 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs new file mode 100644 index 00000000..d3663878 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + interface IPollManager + { + bool IsCompatible(PollEvent evnt); + + LinuxError Poll(List<PollEvent> events, int timeoutMilliseconds, out int updatedCount); + + LinuxError Select(List<PollEvent> events, int timeout, out int updatedCount); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs new file mode 100644 index 00000000..96602830 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs @@ -0,0 +1,155 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum LinuxError + { + SUCCESS = 0, + EPERM = 1 /* Operation not permitted */, + ENOENT = 2 /* No such file or directory */, + ESRCH = 3 /* No such process */, + EINTR = 4 /* Interrupted system call */, + EIO = 5 /* I/O error */, + ENXIO = 6 /* No such device or address */, + E2BIG = 7 /* Argument list too long */, + ENOEXEC = 8 /* Exec format error */, + EBADF = 9 /* Bad file number */, + ECHILD = 10 /* No child processes */, + EAGAIN = 11 /* Try again */, + ENOMEM = 12 /* Out of memory */, + EACCES = 13 /* Permission denied */, + EFAULT = 14 /* Bad address */, + ENOTBLK = 15 /* Block device required */, + EBUSY = 16 /* Device or resource busy */, + EEXIST = 17 /* File exists */, + EXDEV = 18 /* Cross-device link */, + ENODEV = 19 /* No such device */, + ENOTDIR = 20 /* Not a directory */, + EISDIR = 21 /* Is a directory */, + EINVAL = 22 /* Invalid argument */, + ENFILE = 23 /* File table overflow */, + EMFILE = 24 /* Too many open files */, + ENOTTY = 25 /* Not a typewriter */, + ETXTBSY = 26 /* Text file busy */, + EFBIG = 27 /* File too large */, + ENOSPC = 28 /* No space left on device */, + ESPIPE = 29 /* Illegal seek */, + EROFS = 30 /* Read-only file system */, + EMLINK = 31 /* Too many links */, + EPIPE = 32 /* Broken pipe */, + EDOM = 33 /* Math argument out of domain of func */, + ERANGE = 34 /* Math result not representable */, + EDEADLK = 35 /* Resource deadlock would occur */, + ENAMETOOLONG = 36 /* File name too long */, + ENOLCK = 37 /* No record locks available */, + + /* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ + ENOSYS = 38 /* Invalid system call number */, + ENOTEMPTY = 39 /* Directory not empty */, + ELOOP = 40 /* Too many symbolic links encountered */, + EWOULDBLOCK = EAGAIN /* Operation would block */, + ENOMSG = 42 /* No message of desired type */, + EIDRM = 43 /* Identifier removed */, + ECHRNG = 44 /* Channel number out of range */, + EL2NSYNC = 45 /* Level 2 not synchronized */, + EL3HLT = 46 /* Level 3 halted */, + EL3RST = 47 /* Level 3 reset */, + ELNRNG = 48 /* Link number out of range */, + EUNATCH = 49 /* Protocol driver not attached */, + ENOCSI = 50 /* No CSI structure available */, + EL2HLT = 51 /* Level 2 halted */, + EBADE = 52 /* Invalid exchange */, + EBADR = 53 /* Invalid request descriptor */, + EXFULL = 54 /* Exchange full */, + ENOANO = 55 /* No anode */, + EBADRQC = 56 /* Invalid request code */, + EBADSLT = 57 /* Invalid slot */, + EDEADLOCK = EDEADLK, + EBFONT = 59 /* Bad font file format */, + ENOSTR = 60 /* Device not a stream */, + ENODATA = 61 /* No data available */, + ETIME = 62 /* Timer expired */, + ENOSR = 63 /* Out of streams resources */, + ENONET = 64 /* Machine is not on the network */, + ENOPKG = 65 /* Package not installed */, + EREMOTE = 66 /* Object is remote */, + ENOLINK = 67 /* Link has been severed */, + EADV = 68 /* Advertise error */, + ESRMNT = 69 /* Srmount error */, + ECOMM = 70 /* Communication error on send */, + EPROTO = 71 /* Protocol error */, + EMULTIHOP = 72 /* Multihop attempted */, + EDOTDOT = 73 /* RFS specific error */, + EBADMSG = 74 /* Not a data message */, + EOVERFLOW = 75 /* Value too large for defined data type */, + ENOTUNIQ = 76 /* Name not unique on network */, + EBADFD = 77 /* File descriptor in bad state */, + EREMCHG = 78 /* Remote address changed */, + ELIBACC = 79 /* Can not access a needed shared library */, + ELIBBAD = 80 /* Accessing a corrupted shared library */, + ELIBSCN = 81 /* .lib section in a.out corrupted */, + ELIBMAX = 82 /* Attempting to link in too many shared libraries */, + ELIBEXEC = 83 /* Cannot exec a shared library directly */, + EILSEQ = 84 /* Illegal byte sequence */, + ERESTART = 85 /* Interrupted system call should be restarted */, + ESTRPIPE = 86 /* Streams pipe error */, + EUSERS = 87 /* Too many users */, + ENOTSOCK = 88 /* Socket operation on non-socket */, + EDESTADDRREQ = 89 /* Destination address required */, + EMSGSIZE = 90 /* Message too long */, + EPROTOTYPE = 91 /* Protocol wrong type for socket */, + ENOPROTOOPT = 92 /* Protocol not available */, + EPROTONOSUPPORT = 93 /* Protocol not supported */, + ESOCKTNOSUPPORT = 94 /* Socket type not supported */, + EOPNOTSUPP = 95 /* Operation not supported on transport endpoint */, + EPFNOSUPPORT = 96 /* Protocol family not supported */, + EAFNOSUPPORT = 97 /* Address family not supported by protocol */, + EADDRINUSE = 98 /* Address already in use */, + EADDRNOTAVAIL = 99 /* Cannot assign requested address */, + ENETDOWN = 100 /* Network is down */, + ENETUNREACH = 101 /* Network is unreachable */, + ENETRESET = 102 /* Network dropped connection because of reset */, + ECONNABORTED = 103 /* Software caused connection abort */, + ECONNRESET = 104 /* Connection reset by peer */, + ENOBUFS = 105 /* No buffer space available */, + EISCONN = 106 /* Transport endpoint is already connected */, + ENOTCONN = 107 /* Transport endpoint is not connected */, + ESHUTDOWN = 108 /* Cannot send after transport endpoint shutdown */, + ETOOMANYREFS = 109 /* Too many references: cannot splice */, + ETIMEDOUT = 110 /* Connection timed out */, + ECONNREFUSED = 111 /* Connection refused */, + EHOSTDOWN = 112 /* Host is down */, + EHOSTUNREACH = 113 /* No route to host */, + EALREADY = 114 /* Operation already in progress */, + EINPROGRESS = 115 /* Operation now in progress */, + ESTALE = 116 /* Stale file handle */, + EUCLEAN = 117 /* Structure needs cleaning */, + ENOTNAM = 118 /* Not a XENIX named type file */, + ENAVAIL = 119 /* No XENIX semaphores available */, + EISNAM = 120 /* Is a named type file */, + EREMOTEIO = 121 /* Remote I/O error */, + EDQUOT = 122 /* Quota exceeded */, + ENOMEDIUM = 123 /* No medium found */, + EMEDIUMTYPE = 124 /* Wrong medium type */, + ECANCELED = 125 /* Operation Canceled */, + ENOKEY = 126 /* Required key not available */, + EKEYEXPIRED = 127 /* Key has expired */, + EKEYREVOKED = 128 /* Key has been revoked */, + EKEYREJECTED = 129 /* Key was rejected by service */, + + /* for robust mutexes */ + EOWNERDEAD = 130 /* Owner died */, + ENOTRECOVERABLE = 131 /* State not recoverable */, + + ERFKILL = 132 /* Operation not possible due to RF-kill */, + + EHWPOISON = 133 /* Memory page has hardware error */ + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs new file mode 100644 index 00000000..8b77a6c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class PollEvent + { + public PollEventData Data; + public IFileDescriptor FileDescriptor { get; } + + public PollEvent(PollEventData data, IFileDescriptor fileDescriptor) + { + Data = data; + FileDescriptor = fileDescriptor; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs new file mode 100644 index 00000000..546b738e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + struct PollEventData + { +#pragma warning disable CS0649 + public int SocketFd; + public PollEventTypeMask InputEvents; +#pragma warning restore CS0649 + public PollEventTypeMask OutputEvents; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs new file mode 100644 index 00000000..f434fa03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum PollEventTypeMask : ushort + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs new file mode 100644 index 00000000..690a63ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + public struct TimeVal + { + public ulong TvSec; + public ulong TvUsec; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs new file mode 100644 index 00000000..f5877697 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:c")] + class IEthInterface : IpcService + { + public IEthInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs new file mode 100644 index 00000000..9832e448 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:i")] + class IEthInterfaceGroup : IpcService + { + public IEthInterfaceGroup(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs new file mode 100644 index 00000000..0b7adff4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs @@ -0,0 +1,402 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + [Service("nsd:a")] // Max sessions: 5 + [Service("nsd:u")] // Max sessions: 20 + class IManager : IpcService + { + public static readonly NsdSettings NsdSettings; + private readonly FqdnResolver _fqdnResolver; + + private bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + _fqdnResolver = new FqdnResolver(); + + _isInitialized = true; + } + + static IManager() + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + if (!NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode)) + { + // return ResultCode.InvalidSettingsValue; + } + + if (!NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier)) + { + // return ResultCode.InvalidSettingsValue; + } + + NsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode, + Environment = (string)environmentIdentifier + }; + } + + [CommandCmif(5)] // 11.0.0+ + // GetSettingUrl() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetSettingUrl(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(10)] + // GetSettingName() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] + // GetEnvironmentIdentifier() -> buffer<bytes<8> environment_identifier, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier); + + context.Memory.Write(outputPosition, identifierBuffer); + } + + return result; + } + + [CommandCmif(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [CommandCmif(13)] + // DeleteSettings(u32) + public ResultCode DeleteSettings(ServiceCtx context) + { + uint unknown = context.RequestData.ReadUInt32(); + + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + if (unknown > 1) + { + return ResultCode.InvalidArgument; + } + + if (unknown == 1) + { + NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier); + + if ((string)environmentIdentifier == NsdSettings.Environment) + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + else + { + // TODO: Mount the savedata file and return ResultCode. + } + } + else + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + + return ResultCode.Success; + } + + [CommandCmif(14)] + // ImportSettings(u32, buffer<unknown, 5>) -> buffer<unknown, 6> + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(15)] // 4.0.0+ + // SetChangeEnvironmentIdentifierDisabled(bytes<1>) + public ResultCode SetChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + byte disabled = context.RequestData.ReadByte(); + + // TODO: When sys:set service calls will be implemented + /* + if (((nn::settings::detail::GetServiceDiscoveryControlSettings() ^ disabled) & 1) != 0 ) + { + nn::settings::detail::SetServiceDiscoveryControlSettings(disabled & 1); + } + */ + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // Resolve(buffer<unknown<0x100>, 0x15>) -> buffer<unknown<0x100>, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out _, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + return result; + } + + [CommandCmif(21)] + // ResolveEx(buffer<unknown<0x100>, 0x15>) -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [CommandCmif(30)] + // GetNasServiceSetting(buffer<unknown<0x10>, 0x15>) -> buffer<unknown<0x108>, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(31)] + // GetNasServiceSettingEx(buffer<unknown<0x10>, 0x15>) -> (u32, buffer<unknown<0x108>, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(40)] + // GetNasRequestFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(41)] + // GetNasRequestFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(42)] + // GetNasApiFqdn() -> buffer<unknown<0x100>, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(43)] + // GetNasApiFqdnEx() -> (u32, buffer<unknown<0x100>, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(50)] + // GetCurrentSetting() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(51)] // 9.0.0+ + // WriteTestParameter(buffer<?>) + public ResultCode WriteTestParameter(ServiceCtx context) + { + // TODO: Write test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(52)] // 9.0.0+ + // ReadTestParameter() -> buffer<?> + public ResultCode ReadTestParameter(ServiceCtx context) + { + // TODO: Read test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(60)] + // ReadSaveDataFromFsForTest() -> buffer<unknown<0x12bf0>, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Read the savedata 0x80000000000000B0 (nsdsave:/file) and write it inside the buffer. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // WriteSaveDataToFsForTest(buffer<unknown<0x12bf0>, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Write the buffer inside the savedata 0x80000000000000B0 (nsdsave:/file). + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Delete the savedata 0x80000000000000B0. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(63)] // 4.0.0+ + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + // TODO: When sys:set service calls will be implemented use nn::settings::detail::GetServiceDiscoveryControlSettings() + + bool disabled = false; + + context.ResponseData.Write(disabled); + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 10.0.0+ + // GetApplicationServerEnvironmentType() -> bytes<1> + public ResultCode GetApplicationServerEnvironmentType(ServiceCtx context) + { + // TODO: Mount the savedata 0x80000000000000B0 (nsdsave:/test_parameter) and returns the environment type stored inside if the mount succeed. + // Returns ResultCode.NullOutputObject if failed. + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result != ResultCode.Success) + { + return result; + } + + byte environmentType = identifier.AsSpan(0, 2) switch + { + "lp" => (byte)ApplicationServerEnvironmentType.Lp, + "sd" => (byte)ApplicationServerEnvironmentType.Sd, + "sp" => (byte)ApplicationServerEnvironmentType.Sp, + "dp" => (byte)ApplicationServerEnvironmentType.Dp, + _ => (byte)ApplicationServerEnvironmentType.None + }; + + context.ResponseData.Write(environmentType); + + return ResultCode.Success; + } + + [CommandCmif(101)] // 10.0.0+ + // SetApplicationServerEnvironmentType(bytes<1>) + public ResultCode SetApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(102)] // 10.0.0+ + // DeleteApplicationServerEnvironmentType() + public ResultCode DeleteApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs new file mode 100644 index 00000000..4096e431 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs @@ -0,0 +1,97 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager +{ + class FqdnResolver + { + private const string _dummyAddress = "unknown.dummy.nintendo.net"; + + public ResultCode GetEnvironmentIdentifier(out string identifier) + { + if (IManager.NsdSettings.TestMode) + { + identifier = "err"; + + return ResultCode.InvalidSettingsValue; + } + else + { + identifier = IManager.NsdSettings.Environment; + } + + return ResultCode.Success; + } + + public static ResultCode Resolve(string address, out string resolvedAddress) + { + if (address == "api.sect.srv.nintendo.net" || + address == "ctest.cdn.nintendo.net" || + address == "ctest.cdn.n.nintendoswitch.cn" || + address == "unknown.dummy.nintendo.net") + { + resolvedAddress = address; + } + else + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", IManager.NsdSettings.Environment); + + resolvedAddress = ""; + + if (IManager.NsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!IManager.NsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + resolvedAddress = address switch + { + "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // dp1 environment + "api.accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // dp1 environment + "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // lp1 environment + "accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // lp1 environment + /* + // TODO: Determine fields of the struct. + this + 0xEB8 => this + 0xEB8 + 0x300 + this + 0x2BE8 => this + 0x2BE8 + 0x300 + */ + _ => address, + }; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] addressBuffer = new byte[inputSize]; + + context.Memory.Read(inputPosition, addressBuffer); + + string address = Encoding.UTF8.GetString(addressBuffer).TrimEnd('\0'); + + resultCode = Resolve(address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = _dummyAddress; + } + + if (IManager.NsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs new file mode 100644 index 00000000..993fbe8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + enum ResultCode + { + ModuleId = 141, + ErrorCodeShift = 9, + + Success = 0, + + InvalidSettingsValue = ( 1 << ErrorCodeShift) | ModuleId, + InvalidObject1 = ( 3 << ErrorCodeShift) | ModuleId, + InvalidObject2 = ( 4 << ErrorCodeShift) | ModuleId, + NullOutputObject = ( 5 << ErrorCodeShift) | ModuleId, + SettingsNotLoaded = ( 6 << ErrorCodeShift) | ModuleId, + InvalidArgument = ( 8 << ErrorCodeShift) | ModuleId, + SettingsNotInitialized = ( 10 << ErrorCodeShift) | ModuleId, + ServiceNotInitialized = (400 << ErrorCodeShift) | ModuleId, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs new file mode 100644 index 00000000..150bdab4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types +{ + enum ApplicationServerEnvironmentType : byte + { + None, + Lp, + Sd, + Sp, + Dp + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs new file mode 100644 index 00000000..0a72fa87 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + class NsdSettings + { + public bool Initialized; + public bool TestMode; + public string Environment; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs new file mode 100644 index 00000000..64c3acbb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -0,0 +1,686 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types; +using Ryujinx.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + [Service("sfdnsres")] + class IResolver : IpcService + { + public IResolver(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + } + + [CommandCmif(0)] + // SetDnsAddressesPrivateRequest(u32, buffer<unknown, 5, 0>) + public ResultCode SetDnsAddressesPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(1)] + // GetDnsAddressPrivateRequest(u32) -> buffer<unknown, 6, 0> + public ResultCode GetDnsAddressPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(2)] + // GetHostByNameRequest(u8, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByNameRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByNameRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(3)] + // GetHostByAddrRequest(u32, u32, u32, u64, pid, buffer<unknown, 5, 0>) -> (u32, u32, u32, buffer<unknown, 6, 0>) + public ResultCode GetHostByAddrRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByAddrRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(4)] + // GetHostStringErrorRequest(u32) -> buffer<unknown, 6, 0> + public ResultCode GetHostStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + + string errorString = errorCode switch + { + NetDbError.Success => "Resolver Error 0 (no error)", + NetDbError.HostNotFound => "Unknown host", + NetDbError.TryAgain => "Host name lookup failure", + NetDbError.NoRecovery => "Unknown server error", + NetDbError.NoData => "No address associated with name", + _ => (errorCode <= NetDbError.Internal) ? "Resolver internal error" : "Unknown resolver error" + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(5)] + // GetGaiStringErrorRequest(u32) -> buffer<byte, 6, 0> + public ResultCode GetGaiStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + string errorString = errorCode switch + { + GaiError.AddressFamily => "Address family for hostname not supported", + GaiError.Again => "Temporary failure in name resolution", + GaiError.BadFlags => "Invalid value for ai_flags", + GaiError.Fail => "Non-recoverable failure in name resolution", + GaiError.Family => "ai_family not supported", + GaiError.Memory => "Memory allocation failure", + GaiError.NoData => "No address associated with hostname", + GaiError.NoName => "hostname nor servname provided, or not known", + GaiError.Service => "servname not supported for ai_socktype", + GaiError.SocketType => "ai_socktype not supported", + GaiError.System => "System error returned in errno", + GaiError.BadHints => "Invalid value for hints", + GaiError.Protocol => "Resolved protocol is unknown", + GaiError.Overflow => "Argument buffer overflow", + GaiError.Max => "Unknown error", + _ => "Success" + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(6)] + // GetAddrInfoRequest(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints) -> (i32 ret, u32 bsd_errno, u32 packed_addrinfo_size, buffer<packed_addrinfo, 6, 0> response) + public ResultCode GetAddrInfoRequest(ServiceCtx context) + { + ulong responseBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong responseBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetAddrInfoRequestImpl(context, responseBufferPosition, responseBufferSize, false, 0, 0); + } + + [CommandCmif(8)] + // GetCancelHandleRequest(u64, pid) -> u32 + public ResultCode GetCancelHandleRequest(ServiceCtx context) + { + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + uint cancelHandleRequest = 0; + + context.ResponseData.Write(cancelHandleRequest); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // CancelRequest(u32, u64, pid) + public ResultCode CancelRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 5.0.0+ + // GetHostByNameRequestWithOptions(u8, u32, u64, pid, buffer<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>) + public ResultCode GetHostByNameRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByNameRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(11)] // 5.0.0+ + // GetHostByAddrRequestWithOptions(u32, u32, u32, u64, pid, buffer<unknown, 21, 0>, buffer<unknown, 21, 0>) -> (u32, u32, u32, buffer<unknown, 22, 0>) + public ResultCode GetHostByAddrRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByAddrRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(12)] // 5.0.0+ + // GetAddrInfoRequestWithOptions(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer<i8, 5, 0> host, buffer<i8, 5, 0> service, buffer<packed_addrinfo, 5, 0> hints, buffer<unknown, 21, 0>) -> (i32 ret, u32 bsd_errno, u32 unknown, u32 packed_addrinfo_size, buffer<packed_addrinfo, 22, 0> response) + public ResultCode GetAddrInfoRequestWithOptions(ServiceCtx context) + { + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetAddrInfoRequestImpl(context, outputBufferPosition, outputBufferSize, true, optionsBufferPosition, optionsBufferSize); + } + + [CommandCmif(14)] // 5.0.0+ + // ResolverSetOptionRequest(buffer<unknown, 5, 0>, u64 unknown, u64 pid_placeholder, pid) -> (i32 ret, u32 bsd_errno) + public ResultCode ResolverSetOptionRequest(ServiceCtx context) + { + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + ulong unknown = context.RequestData.ReadUInt64(); + + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + // TODO: Parse and use options. + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown }); + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Success; + + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + + return ResultCode.Success; + } + + // Atmosphère extension for dns_mitm + [CommandCmif(65000)] + // AtmosphereReloadHostsFile() + public ResultCode AtmosphereReloadHostsFile(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + + return ResultCode.Success; + } + + private static ResultCode GetHostByNameRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + string host = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // TODO: Use params. + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Overflow; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.HostNotFound; + } + + if (hostEntry != null) + { + IEnumerable<IPAddress> addresses = GetIpv4Addresses(hostEntry); + + if (!addresses.Any()) + { + errno = GaiError.NoData; + netDbErrorCode = NetDbError.NoAddress; + } + else + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, addresses); + } + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static ResultCode GetHostByAddrRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked."); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + byte[] rawIp = new byte[inputBufferSize]; + + context.Memory.Read(inputBufferPosition, rawIp); + + // TODO: Use params. + uint socketLength = context.RequestData.ReadUInt32(); + uint type = context.RequestData.ReadUInt32(); + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (rawIp.Length == 4) + { + try + { + IPAddress address = new IPAddress(rawIp); + + hostEntry = Dns.GetHostEntry(address); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, GetIpv4Addresses(hostEntry)); + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static int SerializeHostEntries(ServiceCtx context, ulong outputBufferPosition, ulong outputBufferSize, IPHostEntry hostEntry, IEnumerable<IPAddress> addresses = null) + { + ulong originalBufferPosition = outputBufferPosition; + ulong bufferPosition = originalBufferPosition; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += (ulong)hostName.Length; + + // h_aliases list size + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(hostEntry.Aliases.Length)); + bufferPosition += sizeof(int); + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += (ulong)(alias.Length + 1); + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)AddressFamily.InterNetwork)); + bufferPosition += sizeof(short); + + // h_length but it's a short + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)4)); + bufferPosition += sizeof(short); + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.Write(bufferPosition, addresses != null ? BinaryPrimitives.ReverseEndianness(addresses.Count()) : 0); + bufferPosition += sizeof(int); + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += sizeof(int); + } + } + + return (int)(bufferPosition - originalBufferPosition); + } + + private static ResultCode GetAddrInfoRequestImpl( + ServiceCtx context, + ulong responseBufferPosition, + ulong responseBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; + uint cancelHandle = context.RequestData.ReadUInt32(); + + string host = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[0].Position, (long)context.Request.SendBuff[0].Size); + string service = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[1].Position, (long)context.Request.SendBuff[1].Size); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // NOTE: We ignore hints for now. + List<AddrInfoSerialized> hints = DeserializeAddrInfos(context.Memory, context.Request.SendBuff[2].Position, context.Request.SendBuff[2].Size); + + if (withOptions) + { + // TODO: Find unknown, Parse and use options. + uint unknown = context.RequestData.ReadUInt32(); + } + + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + int.TryParse(service, out int port); + + errno = GaiError.Success; + serializedSize = SerializeAddrInfos(context, responseBufferPosition, responseBufferSize, hostEntry, port); + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static List<AddrInfoSerialized> DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size) + { + List<AddrInfoSerialized> result = new(); + + ReadOnlySpan<byte> data = memory.GetSpan(address, (int)size); + + while (!data.IsEmpty) + { + AddrInfoSerialized info = AddrInfoSerialized.Read(data, out data); + + if (info == null) + { + break; + } + + result.Add(info); + } + + return result; + } + + private static int SerializeAddrInfos(ServiceCtx context, ulong responseBufferPosition, ulong responseBufferSize, IPHostEntry hostEntry, int port) + { + ulong originalBufferPosition = responseBufferPosition; + ulong bufferPosition = originalBufferPosition; + + byte[] hostName = Encoding.ASCII.GetBytes(hostEntry.HostName + '\0'); + + using (WritableRegion region = context.Memory.GetWritableRegion(responseBufferPosition, (int)responseBufferSize)) + { + Span<byte> data = region.Memory.Span; + + for (int i = 0; i < hostEntry.AddressList.Length; i++) + { + IPAddress ip = hostEntry.AddressList[i]; + + if (ip.AddressFamily != AddressFamily.InterNetwork) + { + continue; + } + + // NOTE: 0 = Any + AddrInfoSerializedHeader header = new(ip, 0); + AddrInfo4 addr = new(ip, (short)port); + AddrInfoSerialized info = new(header, addr, null, hostEntry.HostName); + + data = info.Write(data); + } + + uint sentinel = 0; + MemoryMarshal.Write(data, ref sentinel); + data = data[sizeof(uint)..]; + + return region.Memory.Span.Length - data.Length; + } + } + + private static void WriteResponse( + ServiceCtx context, + bool withOptions, + int serializedSize, + GaiError errno, + NetDbError netDbErrorCode) + { + if (withOptions) + { + context.ResponseData.Write(serializedSize); + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write(0); + } + else + { + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + } + } + + private static IEnumerable<IPAddress> GetIpv4Addresses(IPHostEntry hostEntry) + { + return hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork); + } + + private static NetDbError ConvertSocketErrorCodeToNetDbError(int errorCode) + { + return errorCode switch + { + 11001 => NetDbError.HostNotFound, + 11002 => NetDbError.TryAgain, + 11003 => NetDbError.NoRecovery, + 11004 => NetDbError.NoData, + _ => NetDbError.Internal + }; + } + + private static GaiError ConvertSocketErrorCodeToGaiError(int errorCode, GaiError errno) + { + return errorCode switch + { + 11001 => GaiError.NoData, + 10060 => GaiError.Again, + _ => errno + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs new file mode 100644 index 00000000..776a6f7c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs @@ -0,0 +1,44 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + static partial class DnsBlacklist + { + const RegexOptions RegexOpts = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + [GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost1(); + [GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost2(); + [GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost3(); + [GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost4(); + [GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost5(); + [GeneratedRegex(@"^accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost6(); + + private static readonly Regex[] BlockedHosts = { + BlockedHost1(), + BlockedHost2(), + BlockedHost3(), + BlockedHost4(), + BlockedHost5(), + BlockedHost6() + }; + + public static bool IsHostBlocked(string host) + { + foreach (Regex regex in BlockedHosts) + { + if (regex.IsMatch(host)) + { + return true; + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs new file mode 100644 index 00000000..8eece5ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + class DnsMitmResolver + { + private const string HostsFilePath = "/atmosphere/hosts/default.txt"; + + private static DnsMitmResolver _instance; + public static DnsMitmResolver Instance => _instance ??= new DnsMitmResolver(); + + private readonly Dictionary<string, IPAddress> _mitmHostEntries = new(); + + public void ReloadEntries(ServiceCtx context) + { + string sdPath = context.Device.Configuration.VirtualFileSystem.GetSdCardPath(); + string filePath = context.Device.Configuration.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath); + + _mitmHostEntries.Clear(); + + if (File.Exists(filePath)) + { + using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using StreamReader reader = new(fileStream); + + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + + if (line == null) + { + break; + } + + // Ignore comments and empty lines + if (line.StartsWith("#") || line.Trim().Length == 0) + { + continue; + } + + string[] entry = line.Split(new[] { ' ', '\t' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + // Hosts file example entry: + // 127.0.0.1 localhost loopback + + // 0. Check the size of the array + if (entry.Length < 2) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid entry in hosts file: {line}"); + + continue; + } + + // 1. Parse the address + if (!IPAddress.TryParse(entry[0], out IPAddress address)) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Failed to parse IP address in hosts file: {entry[0]}"); + + continue; + } + + // 2. Check for AMS hosts file extension: "%" + for (int i = 1; i < entry.Length; i++) + { + entry[i] = entry[i].Replace("%", IManager.NsdSettings.Environment); + } + + // 3. Add hostname to entry dictionary (updating duplicate entries) + foreach (string hostname in entry[1..]) + { + _mitmHostEntries[hostname] = address; + } + } + } + } + + public IPHostEntry ResolveAddress(string host) + { + foreach (var hostEntry in _mitmHostEntries) + { + // Check for AMS hosts file extension: "*" + // NOTE: MatchesSimpleExpression also allows "?" as a wildcard + if (FileSystemName.MatchesSimpleExpression(hostEntry.Key, host)) + { + Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Redirecting '{host}' to: {hostEntry.Value}"); + + return new IPHostEntry + { + AddressList = new[] { hostEntry.Value }, + HostName = hostEntry.Key, + Aliases = Array.Empty<string>() + }; + } + } + + // No match has been found, resolve the host using regular dns + return Dns.GetHostEntry(host); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs new file mode 100644 index 00000000..0a20e057 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct AddrInfo4 + { + public byte Length; + public byte Family; + public short Port; + public Array4<byte> Address; + public Array8<byte> Padding; + + public AddrInfo4(IPAddress address, short port) + { + Length = (byte)Unsafe.SizeOf<Array4<byte>>(); + Family = (byte)AddressFamily.InterNetwork; + Port = IPAddress.HostToNetworkOrder(port); + Address = new Array4<byte>(); + + address.TryWriteBytes(Address.AsSpan(), out _); + } + + public void ToNetworkOrder() + { + Port = IPAddress.HostToNetworkOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public void ToHostOrder() + { + Port = IPAddress.NetworkToHostOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public static void RawIpv4AddressNetworkEndianSwap(ref Array4<byte> address) + { + if (BitConverter.IsLittleEndian) + { + address.AsSpan().Reverse(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs new file mode 100644 index 00000000..a0613d7b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Utilities; +using System; +using System.Diagnostics; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + class AddrInfoSerialized + { + public AddrInfoSerializedHeader Header; + public AddrInfo4? SocketAddress; + public Array4<byte>? RawIPv4Address; + public string CanonicalName; + + public AddrInfoSerialized(AddrInfoSerializedHeader header, AddrInfo4? address, Array4<byte>? rawIPv4Address, string canonicalName) + { + Header = header; + SocketAddress = address; + RawIPv4Address = rawIPv4Address; + CanonicalName = canonicalName; + } + + public static AddrInfoSerialized Read(ReadOnlySpan<byte> buffer, out ReadOnlySpan<byte> rest) + { + if (!MemoryMarshal.TryRead(buffer, out AddrInfoSerializedHeader header)) + { + rest = buffer; + + return null; + } + + AddrInfo4? socketAddress = null; + Array4<byte>? rawIPv4Address = null; + string canonicalName; + + buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..]; + + header.ToHostOrder(); + + if (header.Magic != SfdnsresContants.AddrInfoMagic) + { + rest = buffer; + + return null; + } + + Debug.Assert(header.Magic == SfdnsresContants.AddrInfoMagic); + + if (header.AddressLength == 0) + { + rest = buffer; + + return null; + } + + if (header.Family == (int)AddressFamily.InterNetwork) + { + socketAddress = MemoryMarshal.Read<AddrInfo4>(buffer); + socketAddress.Value.ToHostOrder(); + + buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..]; + } + // AF_INET6 + else if (header.Family == 28) + { + throw new NotImplementedException(); + } + else + { + // Nintendo hardcode 4 bytes in that case here. + Array4<byte> address = MemoryMarshal.Read<Array4<byte>>(buffer); + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref address); + + rawIPv4Address = address; + + buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..]; + } + + canonicalName = StringUtils.ReadUtf8String(buffer, out int dataRead); + buffer = buffer[dataRead..]; + + rest = buffer; + + return new AddrInfoSerialized(header, socketAddress, rawIPv4Address, canonicalName); + } + + public Span<byte> Write(Span<byte> buffer) + { + int familly = Header.Family; + + Header.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, ref Header); + + buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..]; + + if (familly == (int)AddressFamily.InterNetwork) + { + AddrInfo4 socketAddress = SocketAddress.Value; + socketAddress.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, ref socketAddress); + + buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..]; + } + // AF_INET6 + else if (familly == 28) + { + throw new NotImplementedException(); + } + else + { + Array4<byte> rawIPv4Address = RawIPv4Address.Value; + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address); + + MemoryMarshal.Write(buffer, ref rawIPv4Address); + + buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..]; + } + + if (CanonicalName == null) + { + buffer[0] = 0; + + buffer = buffer[1..]; + } + else + { + byte[] canonicalName = Encoding.ASCII.GetBytes(CanonicalName + '\0'); + + canonicalName.CopyTo(buffer); + + buffer = buffer[canonicalName.Length..]; + } + + return buffer; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs new file mode 100644 index 00000000..8e304dfa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs @@ -0,0 +1,57 @@ +using Ryujinx.Common.Memory; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6 * sizeof(int))] + struct AddrInfoSerializedHeader + { + public uint Magic; + public int Flags; + public int Family; + public int SocketType; + public int Protocol; + public uint AddressLength; + + public AddrInfoSerializedHeader(IPAddress address, SocketType socketType) + { + Magic = SfdnsresContants.AddrInfoMagic; + Flags = 0; + Family = (int)address.AddressFamily; + SocketType = (int)socketType; + Protocol = 0; + + if (address.AddressFamily == AddressFamily.InterNetwork) + { + AddressLength = (uint)Unsafe.SizeOf<AddrInfo4>(); + } + else + { + AddressLength = (uint)Unsafe.SizeOf<Array4<byte>>(); + } + } + + public void ToNetworkOrder() + { + Magic = (uint)IPAddress.HostToNetworkOrder((int)Magic); + Flags = IPAddress.HostToNetworkOrder(Flags); + Family = IPAddress.HostToNetworkOrder(Family); + SocketType = IPAddress.HostToNetworkOrder(SocketType); + Protocol = IPAddress.HostToNetworkOrder(Protocol); + AddressLength = (uint)IPAddress.HostToNetworkOrder((int)AddressLength); + } + + public void ToHostOrder() + { + Magic = (uint)IPAddress.NetworkToHostOrder((int)Magic); + Flags = IPAddress.NetworkToHostOrder(Flags); + Family = IPAddress.NetworkToHostOrder(Family); + SocketType = IPAddress.NetworkToHostOrder(SocketType); + Protocol = IPAddress.NetworkToHostOrder(Protocol); + AddressLength = (uint)IPAddress.NetworkToHostOrder((int)AddressLength); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs new file mode 100644 index 00000000..f9f28b44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum GaiError + { + Success, + AddressFamily, + Again, + BadFlags, + Fail, + Family, + Memory, + NoData, + NoName, + Service, + SocketType, + System, + BadHints, + Protocol, + Overflow, + Max + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs new file mode 100644 index 00000000..3c04c049 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum NetDbError + { + Internal = -1, + Success, + HostNotFound, + TryAgain, + NoRecovery, + NoData, + NoAddress = NoData + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs new file mode 100644 index 00000000..d194a3c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + static class SfdnsresContants + { + public const uint AddrInfoMagic = 0xBEEFCAFE; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs new file mode 100644 index 00000000..aa350b73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs @@ -0,0 +1,126 @@ +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Spl.Types; + +namespace Ryujinx.HLE.HOS.Services.Spl +{ + [Service("spl:")] + [Service("spl:es")] + [Service("spl:fs")] + [Service("spl:manu")] + [Service("spl:mig")] + [Service("spl:ssl")] + class IGeneralInterface : IpcService + { + public IGeneralInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetConfig(u32 config_item) -> u64 config_value + public ResultCode GetConfig(ServiceCtx context) + { + ConfigItem configItem = (ConfigItem)context.RequestData.ReadUInt32(); + + // NOTE: Nintendo explicitly blacklists package2 hash here, amusingly. + // This is not blacklisted in safemode, but we're never in safe mode... + if (configItem == ConfigItem.Package2Hash) + { + return ResultCode.InvalidArguments; + } + + // TODO: This should call svcCallSecureMonitor using arg 0xC3000002. + // Since it's currently not implemented we can use a private method for now. + SmcResult result = SmcGetConfig(context, out ulong configValue, configItem); + + // Nintendo has some special handling here for hardware type/is_retail. + if (result == SmcResult.InvalidArgument) + { + switch (configItem) + { + case ConfigItem.HardwareType: + configValue = (ulong)HardwareType.Icosa; + result = SmcResult.Success; + break; + case ConfigItem.HardwareState: + configValue = (ulong)HardwareState.Development; + result = SmcResult.Success; + break; + default: + break; + } + } + + context.ResponseData.Write(configValue); + + return (ResultCode)((int)result << 9) | ResultCode.ModuleId; + } + + private SmcResult SmcGetConfig(ServiceCtx context, out ulong configValue, ConfigItem configItem) + { + configValue = default; + + SystemVersion version = context.Device.System.ContentManager.GetCurrentFirmwareVersion(); + MemorySize memorySize = context.Device.Configuration.MemoryConfiguration.ToKernelMemorySize(); + + switch (configItem) + { + case ConfigItem.DisableProgramVerification: + configValue = 0; + break; + case ConfigItem.DramId: + if (memorySize == MemorySize.MemorySize8GiB) + { + configValue = (ulong)DramId.IowaSamsung8GiB; + } + else if (memorySize == MemorySize.MemorySize6GiB) + { + configValue = (ulong)DramId.IcosaSamsung6GiB; + } + else + { + configValue = (ulong)DramId.IcosaSamsung4GiB; + } + break; + case ConfigItem.SecurityEngineInterruptNumber: + return SmcResult.NotImplemented; + case ConfigItem.FuseVersion: + return SmcResult.NotImplemented; + case ConfigItem.HardwareType: + configValue = (ulong)HardwareType.Icosa; + break; + case ConfigItem.HardwareState: + configValue = (ulong)HardwareState.Production; + break; + case ConfigItem.IsRecoveryBoot: + configValue = 0; + break; + case ConfigItem.DeviceId: + return SmcResult.NotImplemented; + case ConfigItem.BootReason: + // This was removed in firmware 4.0.0. + return SmcResult.InvalidArgument; + case ConfigItem.MemoryMode: + configValue = (ulong)context.Device.Configuration.MemoryConfiguration; + break; + case ConfigItem.IsDevelopmentFunctionEnabled: + configValue = 0; + break; + case ConfigItem.KernelConfiguration: + return SmcResult.NotImplemented; + case ConfigItem.IsChargerHiZModeEnabled: + return SmcResult.NotImplemented; + case ConfigItem.QuestState: + return SmcResult.NotImplemented; + case ConfigItem.RegulatorType: + return SmcResult.NotImplemented; + case ConfigItem.DeviceUniqueKeyGeneration: + return SmcResult.NotImplemented; + case ConfigItem.Package2Hash: + return SmcResult.NotImplemented; + default: + return SmcResult.InvalidArgument; + } + + return SmcResult.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs new file mode 100644 index 00000000..c911f434 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs @@ -0,0 +1,38 @@ +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Spl +{ + [Service("csrng")] + class IRandomInterface : DisposableIpcService + { + private RandomNumberGenerator _rng; + + private object _lock = new object(); + + public IRandomInterface(ServiceCtx context) + { + _rng = RandomNumberGenerator.Create(); + } + + [CommandCmif(0)] + // GetRandomBytes() -> buffer<unknown, 6> + public ResultCode GetRandomBytes(ServiceCtx context) + { + byte[] randomBytes = new byte[context.Request.ReceiveBuff[0].Size]; + + _rng.GetBytes(randomBytes); + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, randomBytes); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _rng.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs new file mode 100644 index 00000000..4f61998a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Spl +{ + enum ResultCode + { + ModuleId = 26, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArguments = (101 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs new file mode 100644 index 00000000..f08bbeaa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum ConfigItem + { + // Standard config items. + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineInterruptNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + QuestState = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs new file mode 100644 index 00000000..422c8d69 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum DramId + { + IcosaSamsung4GiB, + IcosaHynix4GiB, + IcosaMicron4GiB, + IowaHynix1y4GiB, + IcosaSamsung6GiB, + HoagHynix1y4GiB, + AulaHynix1y4GiB, + IowaX1X2Samsung4GiB, + IowaSansung4GiB, + IowaSamsung8GiB, + IowaHynix4GiB, + IowaMicron4GiB, + HoagSamsung4GiB, + HoagSamsung8GiB, + HoagHynix4GiB, + HoagMicron4GiB, + IowaSamsung4GiBY, + IowaSamsung1y4GiBX, + IowaSamsung1y8GiBX, + HoagSamsung1y4GiBX, + IowaSamsung1y4GiBY, + IowaSamsung1y8GiBY, + AulaSamsung1y4GiB, + HoagSamsung1y8GiBX, + AulaSamsung1y4GiBX, + IowaMicron1y4GiB, + HoagMicron1y4GiB, + AulaMicron1y4GiB, + AulaSamsung1y8GiBX + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs new file mode 100644 index 00000000..414d0f11 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum HardwareState + { + Development, + Production + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs new file mode 100644 index 00000000..491eb943 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum HardwareType + { + Icosa, + Copper, + Hoag, + Iowa, + Calcio, + Aula + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs new file mode 100644 index 00000000..d5f424a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum SmcResult + { + Success = 0, + NotImplemented = 1, + InvalidArgument = 2, + Busy = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + NotInitialized = 7, + + PsciSuccess = 0, + PsciNotSupported = -1, + PsciInvalidParameters = -2, + PsciDenied = -3, + PsciAlreadyOn = -4 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs b/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs new file mode 100644 index 00000000..167dea67 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Srepo +{ + [Service("srepo:a")] // 5.0.0+ + [Service("srepo:u")] // 5.0.0+ + class ISrepoService : IpcService + { + public ISrepoService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs new file mode 100644 index 00000000..abbc1354 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs @@ -0,0 +1,246 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + class BuiltInCertificateManager + { + private const long CertStoreTitleId = 0x0100000000000800; + + private readonly string CertStoreTitleMissingErrorMessage = "CertStore system title not found! SSL CA retrieving will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)"; + + private static BuiltInCertificateManager _instance; + + public static BuiltInCertificateManager Instance + { + get + { + if (_instance == null) + { + _instance = new BuiltInCertificateManager(); + } + + return _instance; + } + } + + private VirtualFileSystem _virtualFileSystem; + private IntegrityCheckLevel _fsIntegrityCheckLevel; + private ContentManager _contentManager; + private bool _initialized; + private Dictionary<CaCertificateId, CertStoreEntry> _certificates; + + private object _lock = new object(); + + private struct CertStoreFileHeader + { + private const uint ValidMagic = 0x546C7373; + +#pragma warning disable CS0649 + public uint Magic; + public uint EntriesCount; +#pragma warning restore CS0649 + + public bool IsValid() + { + return Magic == ValidMagic; + } + } + + private struct CertStoreFileEntry + { +#pragma warning disable CS0649 + public CaCertificateId Id; + public TrustedCertStatus Status; + public uint DataSize; + public uint DataOffset; +#pragma warning restore CS0649 + } + + public class CertStoreEntry + { + public CaCertificateId Id; + public TrustedCertStatus Status; + public byte[] Data; + } + + public string GetCertStoreTitleContentPath() + { + return _contentManager.GetInstalledContentPath(CertStoreTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + } + + public bool HasCertStoreTitle() + { + return !string.IsNullOrEmpty(GetCertStoreTitleContentPath()); + } + + private CertStoreEntry ReadCertStoreEntry(ReadOnlySpan<byte> buffer, CertStoreFileEntry entry) + { + string customCertificatePath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "ssl", $"{entry.Id}.der"); + + byte[] data; + + if (File.Exists(customCertificatePath)) + { + data = File.ReadAllBytes(customCertificatePath); + } + else + { + data = buffer.Slice((int)entry.DataOffset, (int)entry.DataSize).ToArray(); + } + + return new CertStoreEntry + { + Id = entry.Id, + Status = entry.Status, + Data = data + }; + } + + public void Initialize(Switch device) + { + lock (_lock) + { + _certificates = new Dictionary<CaCertificateId, CertStoreEntry>(); + _initialized = false; + _contentManager = device.System.ContentManager; + _virtualFileSystem = device.FileSystem; + _fsIntegrityCheckLevel = device.System.FsIntegrityCheckLevel; + + if (HasCertStoreTitle()) + { + using LocalStorage ncaFile = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(GetCertStoreTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile); + + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var trustedCertsFileRef = new UniqueRef<IFile>(); + + Result result = romfs.OpenFile(ref trustedCertsFileRef.Ref, "/ssl_TrustedCerts.bdf".ToU8Span(), OpenMode.Read); + + if (!result.IsSuccess()) + { + // [1.0.0 - 2.3.0] + if (ResultFs.PathNotFound.Includes(result)) + { + result = romfs.OpenFile(ref trustedCertsFileRef.Ref, "/ssl_TrustedCerts.tcf".ToU8Span(), OpenMode.Read); + } + + if (result.IsFailure()) + { + Logger.Error?.Print(LogClass.ServiceSsl, CertStoreTitleMissingErrorMessage); + + return; + } + } + + using IFile trustedCertsFile = trustedCertsFileRef.Release(); + + trustedCertsFile.GetSize(out long fileSize).ThrowIfFailure(); + + Span<byte> trustedCertsRaw = new byte[fileSize]; + + trustedCertsFile.Read(out _, 0, trustedCertsRaw).ThrowIfFailure(); + + CertStoreFileHeader header = MemoryMarshal.Read<CertStoreFileHeader>(trustedCertsRaw); + + if (!header.IsValid()) + { + Logger.Error?.Print(LogClass.ServiceSsl, "Invalid CertStore data found, skipping!"); + + return; + } + + ReadOnlySpan<byte> trustedCertsData = trustedCertsRaw[Unsafe.SizeOf<CertStoreFileHeader>()..]; + ReadOnlySpan<CertStoreFileEntry> trustedCertsEntries = MemoryMarshal.Cast<byte, CertStoreFileEntry>(trustedCertsData)[..(int)header.EntriesCount]; + + foreach (CertStoreFileEntry entry in trustedCertsEntries) + { + _certificates.Add(entry.Id, ReadCertStoreEntry(trustedCertsData, entry)); + } + + _initialized = true; + } + } + } + + public bool TryGetCertificates( + ReadOnlySpan<CaCertificateId> ids, + out CertStoreEntry[] entries, + out bool hasAllCertificates, + out int requiredSize) + { + lock (_lock) + { + if (!_initialized) + { + throw new InvalidSystemResourceException(CertStoreTitleMissingErrorMessage); + } + + requiredSize = 0; + hasAllCertificates = false; + + foreach (CaCertificateId id in ids) + { + if (id == CaCertificateId.All) + { + hasAllCertificates = true; + + break; + } + } + + if (hasAllCertificates) + { + entries = new CertStoreEntry[_certificates.Count]; + requiredSize = (_certificates.Count + 1) * Unsafe.SizeOf<BuiltInCertificateInfo>(); + + int i = 0; + + foreach (CertStoreEntry entry in _certificates.Values) + { + entries[i++] = entry; + requiredSize += (entry.Data.Length + 3) & ~3; + } + + return true; + } + else + { + entries = new CertStoreEntry[ids.Length]; + requiredSize = ids.Length * Unsafe.SizeOf<BuiltInCertificateInfo>(); + + for (int i = 0; i < ids.Length; i++) + { + if (!_certificates.TryGetValue(ids[i], out CertStoreEntry entry)) + { + return false; + } + + entries[i] = entry; + requiredSize += (entry.Data.Length + 3) & ~3; + } + + return true; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs new file mode 100644 index 00000000..7741ef7e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs @@ -0,0 +1,125 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ssl.SslService; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + [Service("ssl")] + class ISslService : IpcService + { + // NOTE: The SSL service is used by games to connect it to various official online services, which we do not intend to support. + // In this case it is acceptable to stub all calls of the service. + public ISslService(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext> + public ResultCode CreateContext(ServiceCtx context) + { + SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); + + MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion)); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetCertificates(buffer<CaCertificateId, 5> ids) -> (u32 certificates_count, buffer<bytes, 6> certificates) + public ResultCode GetCertificates(ServiceCtx context) + { + ReadOnlySpan<CaCertificateId> ids = MemoryMarshal.Cast<byte, CaCertificateId>(context.Memory.GetSpan(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size)); + + if (!BuiltInCertificateManager.Instance.TryGetCertificates( + ids, + out BuiltInCertificateManager.CertStoreEntry[] entries, + out bool hasAllCertificates, + out int requiredSize)) + { + throw new InvalidOperationException(); + } + + if ((uint)requiredSize > (uint)context.Request.ReceiveBuff[0].Size) + { + return ResultCode.InvalidCertBufSize; + } + + int infosCount = entries.Length; + + if (hasAllCertificates) + { + infosCount++; + } + + using (WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size)) + { + Span<byte> rawData = region.Memory.Span; + Span<BuiltInCertificateInfo> infos = MemoryMarshal.Cast<byte, BuiltInCertificateInfo>(rawData)[..infosCount]; + Span<byte> certificatesData = rawData[(Unsafe.SizeOf<BuiltInCertificateInfo>() * infosCount)..]; + + for (int i = 0; i < entries.Length; i++) + { + entries[i].Data.CopyTo(certificatesData); + + infos[i] = new BuiltInCertificateInfo + { + Id = entries[i].Id, + Status = entries[i].Status, + CertificateDataSize = (ulong)entries[i].Data.Length, + CertificateDataOffset = (ulong)(rawData.Length - certificatesData.Length) + }; + + certificatesData = certificatesData[entries[i].Data.Length..]; + } + + if (hasAllCertificates) + { + infos[entries.Length] = new BuiltInCertificateInfo + { + Id = CaCertificateId.All, + Status = TrustedCertStatus.Invalid, + CertificateDataSize = 0, + CertificateDataOffset = 0 + }; + } + } + + context.ResponseData.Write(entries.Length); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetCertificateBufSize(buffer<CaCertificateId, 5> ids) -> u32 buffer_size; + public ResultCode GetCertificateBufSize(ServiceCtx context) + { + ReadOnlySpan<CaCertificateId> ids = MemoryMarshal.Cast<byte, CaCertificateId>(context.Memory.GetSpan(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size)); + + if (!BuiltInCertificateManager.Instance.TryGetCertificates(ids, out _, out _, out int requiredSize)) + { + throw new InvalidOperationException(); + } + + context.ResponseData.Write(requiredSize); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // SetInterfaceVersion(u32) + public ResultCode SetInterfaceVersion(ServiceCtx context) + { + // 1 = 3.0.0+, 2 = 5.0.0+, 3 = 6.0.0+ + uint interfaceVersion = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { interfaceVersion }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs new file mode 100644 index 00000000..862c79cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + public enum ResultCode + { + OsModuleId = 123, + ErrorCodeShift = 9, + + Success = 0, + NoSocket = (103 << ErrorCodeShift) | OsModuleId, + InvalidSocket = (106 << ErrorCodeShift) | OsModuleId, + InvalidCertBufSize = (112 << ErrorCodeShift) | OsModuleId, + InvalidOption = (126 << ErrorCodeShift) | OsModuleId, + CertBufferTooSmall = (202 << ErrorCodeShift) | OsModuleId, + AlreadyInUse = (203 << ErrorCodeShift) | OsModuleId, + WouldBlock = (204 << ErrorCodeShift) | OsModuleId, + Timeout = (205 << ErrorCodeShift) | OsModuleId, + ConnectionReset = (209 << ErrorCodeShift) | OsModuleId, + ConnectionAbort = (210 << ErrorCodeShift) | OsModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs new file mode 100644 index 00000000..b9087f40 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs @@ -0,0 +1,519 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using Ryujinx.Memory; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class ISslConnection : IpcService, IDisposable + { + private bool _doNotClockSocket; + private bool _getServerCertChain; + private bool _skipDefaultVerify; + private bool _enableAlpn; + + private SslVersion _sslVersion; + private IoMode _ioMode; + private VerifyOption _verifyOption; + private SessionCacheMode _sessionCacheMode; + private string _hostName; + + private ISslConnectionBase _connection; + private BsdContext _bsdContext; + private readonly ulong _processId; + + private byte[] _nextAplnProto; + + public ISslConnection(ulong processId, SslVersion sslVersion) + { + _processId = processId; + _sslVersion = sslVersion; + _ioMode = IoMode.Blocking; + _sessionCacheMode = SessionCacheMode.None; + _verifyOption = VerifyOption.PeerCa | VerifyOption.HostName; + } + + [CommandCmif(0)] + // SetSocketDescriptor(u32) -> u32 + public ResultCode SetSocketDescriptor(ServiceCtx context) + { + if (_connection != null) + { + return ResultCode.AlreadyInUse; + } + + _bsdContext = BsdContext.GetContext(_processId); + + if (_bsdContext == null) + { + return ResultCode.InvalidSocket; + } + + int inputFd = context.RequestData.ReadInt32(); + + int internalFd = _bsdContext.DuplicateFileDescriptor(inputFd); + + if (internalFd == -1) + { + return ResultCode.InvalidSocket; + } + + InitializeConnection(internalFd); + + int outputFd = inputFd; + + if (_doNotClockSocket) + { + outputFd = -1; + } + + context.ResponseData.Write(outputFd); + + return ResultCode.Success; + } + + private void InitializeConnection(int socketFd) + { + ISocket bsdSocket = _bsdContext.RetrieveSocket(socketFd); + + _connection = new SslManagedSocketConnection(_bsdContext, _sslVersion, socketFd, bsdSocket); + } + + [CommandCmif(1)] + // SetHostName(buffer<bytes, 5>) + public ResultCode SetHostName(ServiceCtx context) + { + ulong hostNameDataPosition = context.Request.SendBuff[0].Position; + ulong hostNameDataSize = context.Request.SendBuff[0].Size; + + byte[] hostNameData = new byte[hostNameDataSize]; + + context.Memory.Read(hostNameDataPosition, hostNameData); + + _hostName = Encoding.ASCII.GetString(hostNameData).Trim('\0'); + + Logger.Info?.Print(LogClass.ServiceSsl, _hostName); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // SetVerifyOption(nn::ssl::sf::VerifyOption) + public ResultCode SetVerifyOption(ServiceCtx context) + { + _verifyOption = (VerifyOption)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _verifyOption }); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetIoMode(nn::ssl::sf::IoMode) + public ResultCode SetIoMode(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + _ioMode = (IoMode)context.RequestData.ReadUInt32(); + + _connection.Socket.Blocking = _ioMode == IoMode.Blocking; + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _ioMode }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSocketDescriptor() -> u32 + public ResultCode GetSocketDescriptor(ServiceCtx context) + { + context.ResponseData.Write(_connection.SocketFd); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetHostName(buffer<bytes, 6>) -> u32 + public ResultCode GetHostName(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Encoding.ASCII.GetBytes(_hostName, region.Memory.Span); + } + + context.ResponseData.Write((uint)_hostName.Length); + + Logger.Info?.Print(LogClass.ServiceSsl, _hostName); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetVerifyOption() -> nn::ssl::sf::VerifyOption + public ResultCode GetVerifyOption(ServiceCtx context) + { + context.ResponseData.Write((uint)_verifyOption); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _verifyOption }); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // GetIoMode() -> nn::ssl::sf::IoMode + public ResultCode GetIoMode(ServiceCtx context) + { + context.ResponseData.Write((uint)_ioMode); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _ioMode }); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // DoHandshake() + public ResultCode DoHandshake(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + return _connection.Handshake(_hostName); + } + + [CommandCmif(9)] + // DoHandshakeGetServerCert() -> (u32, u32, buffer<bytes, 6>) + public ResultCode DoHandshakeGetServerCert(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result = _connection.Handshake(_hostName); + + if (result == ResultCode.Success) + { + if (_getServerCertChain) + { + using (WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size)) + { + result = _connection.GetServerCertificate(_hostName, region.Memory.Span, out uint bufferSize, out uint certificateCount); + + context.ResponseData.Write(bufferSize); + context.ResponseData.Write(certificateCount); + } + } + else + { + context.ResponseData.Write(0); + context.ResponseData.Write(0); + } + } + + return result; + } + + [CommandCmif(10)] + // Read() -> (u32, buffer<bytes, 6>) + public ResultCode Read(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result; + + using (WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size)) + { + // TODO: Better error management. + result = _connection.Read(out int readCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(readCount); + } + } + + return result; + } + + [CommandCmif(11)] + // Write(buffer<bytes, 5>) -> s32 + public ResultCode Write(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + // We don't dispose as this isn't supposed to be modified + WritableRegion region = context.Memory.GetWritableRegion(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size); + + // TODO: Better error management. + ResultCode result = _connection.Write(out int writtenCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(writtenCount); + } + + return result; + } + + [CommandCmif(12)] + // Pending() -> s32 + public ResultCode Pending(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + context.ResponseData.Write(_connection.Pending()); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // Peek() -> (s32, buffer<bytes, 6>) + public ResultCode Peek(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result; + + using (WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size)) + { + // TODO: Better error management. + result = _connection.Peek(out int peekCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(peekCount); + } + } + + return result; + } + + [CommandCmif(14)] + // Poll(nn::ssl::sf::PollEvent poll_event, u32 timeout) -> nn::ssl::sf::PollEvent + public ResultCode Poll(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(15)] + // GetVerifyCertError() + public ResultCode GetVerifyCertError(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(16)] + // GetNeededServerCertBufferSize() -> u32 + public ResultCode GetNeededServerCertBufferSize(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(17)] + // SetSessionCacheMode(nn::ssl::sf::SessionCacheMode) + public ResultCode SetSessionCacheMode(ServiceCtx context) + { + SessionCacheMode sessionCacheMode = (SessionCacheMode)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sessionCacheMode }); + + _sessionCacheMode = sessionCacheMode; + + return ResultCode.Success; + } + + [CommandCmif(18)] + // GetSessionCacheMode() -> nn::ssl::sf::SessionCacheMode + public ResultCode GetSessionCacheMode(ServiceCtx context) + { + context.ResponseData.Write((uint)_sessionCacheMode); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _sessionCacheMode }); + + return ResultCode.Success; + } + + [CommandCmif(19)] + // FlushSessionCache() + public ResultCode FlushSessionCache(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(20)] + // SetRenegotiationMode(nn::ssl::sf::RenegotiationMode) + public ResultCode SetRenegotiationMode(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(21)] + // GetRenegotiationMode() -> nn::ssl::sf::RenegotiationMode + public ResultCode GetRenegotiationMode(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(22)] + // SetOption(b8 value, nn::ssl::sf::OptionType option) + public ResultCode SetOption(ServiceCtx context) + { + bool value = context.RequestData.ReadUInt32() != 0; + OptionType option = (OptionType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { option, value }); + + return SetOption(option, value); + } + + [CommandCmif(23)] + // GetOption(nn::ssl::sf::OptionType) -> b8 + public ResultCode GetOption(ServiceCtx context) + { + OptionType option = (OptionType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { option }); + + ResultCode result = GetOption(option, out bool value); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(value); + } + + return result; + } + + [CommandCmif(24)] + // GetVerifyCertErrors() -> (u32, u32, buffer<bytes, 6>) + public ResultCode GetVerifyCertErrors(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(25)] // 4.0.0+ + // GetCipherInfo(u32) -> buffer<bytes, 6> + public ResultCode GetCipherInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(26)] + // SetNextAlpnProto(buffer<bytes, 5>) -> u32 + public ResultCode SetNextAlpnProto(ServiceCtx context) + { + ulong inputDataPosition = context.Request.SendBuff[0].Position; + ulong inputDataSize = context.Request.SendBuff[0].Size; + + _nextAplnProto = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, _nextAplnProto); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { inputDataSize }); + + return ResultCode.Success; + } + + [CommandCmif(27)] + // GetNextAlpnProto(buffer<bytes, 6>) -> u32 + public ResultCode GetNextAlpnProto(ServiceCtx context) + { + ulong outputDataPosition = context.Request.ReceiveBuff[0].Position; + ulong outputDataSize = context.Request.ReceiveBuff[0].Size; + + context.Memory.Write(outputDataPosition, _nextAplnProto); + + context.ResponseData.Write(_nextAplnProto.Length); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { outputDataSize }); + + return ResultCode.Success; + } + + private ResultCode SetOption(OptionType option, bool value) + { + switch (option) + { + case OptionType.DoNotCloseSocket: + _doNotClockSocket = value; + break; + + case OptionType.GetServerCertChain: + _getServerCertChain = value; + break; + + case OptionType.SkipDefaultVerify: + _skipDefaultVerify = value; + break; + + case OptionType.EnableAlpn: + _enableAlpn = value; + break; + + default: + Logger.Warning?.Print(LogClass.ServiceSsl, $"Unsupported option {option}"); + return ResultCode.InvalidOption; + } + + return ResultCode.Success; + } + + private ResultCode GetOption(OptionType option, out bool value) + { + switch (option) + { + case OptionType.DoNotCloseSocket: + value = _doNotClockSocket; + break; + + case OptionType.GetServerCertChain: + value = _getServerCertChain; + break; + + case OptionType.SkipDefaultVerify: + value = _skipDefaultVerify; + break; + + case OptionType.EnableAlpn: + value = _enableAlpn; + break; + + default: + Logger.Warning?.Print(LogClass.ServiceSsl, $"Unsupported option {option}"); + + value = false; + return ResultCode.InvalidOption; + } + + return ResultCode.Success; + } + + public void Dispose() + { + _connection?.Dispose(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs new file mode 100644 index 00000000..18e03e49 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + interface ISslConnectionBase: IDisposable + { + int SocketFd { get; } + + ISocket Socket { get; } + + ResultCode Handshake(string hostName); + + ResultCode GetServerCertificate(string hostname, Span<byte> certificates, out uint storageSize, out uint certificateCount); + + ResultCode Write(out int writtenCount, ReadOnlyMemory<byte> buffer); + + ResultCode Read(out int readCount, Memory<byte> buffer); + + ResultCode Peek(out int peekCount, Memory<byte> buffer); + + int Pending(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs new file mode 100644 index 00000000..b38ff921 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs @@ -0,0 +1,83 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class ISslContext : IpcService + { + private uint _connectionCount; + + private readonly ulong _processId; + private readonly SslVersion _sslVersion; + private ulong _serverCertificateId; + private ulong _clientCertificateId; + + public ISslContext(ulong processId, SslVersion sslVersion) + { + _processId = processId; + _sslVersion = sslVersion; + } + + [CommandCmif(2)] + // CreateConnection() -> object<nn::ssl::sf::ISslConnection> + public ResultCode CreateConnection(ServiceCtx context) + { + MakeObject(context, new ISslConnection(_processId, _sslVersion)); + + _connectionCount++; + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetConnectionCount() -> u32 count + public ResultCode GetConnectionCount(ServiceCtx context) + { + context.ResponseData.Write(_connectionCount); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _connectionCount }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // ImportServerPki(nn::ssl::sf::CertificateFormat certificateFormat, buffer<bytes, 5> certificate) -> u64 certificateId + public ResultCode ImportServerPki(ServiceCtx context) + { + CertificateFormat certificateFormat = (CertificateFormat)context.RequestData.ReadUInt32(); + + ulong certificateDataPosition = context.Request.SendBuff[0].Position; + ulong certificateDataSize = context.Request.SendBuff[0].Size; + + context.ResponseData.Write(_serverCertificateId++); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { certificateFormat }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // ImportClientPki(buffer<bytes, 5> certificate, buffer<bytes, 5> ascii_password) -> u64 certificateId + public ResultCode ImportClientPki(ServiceCtx context) + { + ulong certificateDataPosition = context.Request.SendBuff[0].Position; + ulong certificateDataSize = context.Request.SendBuff[0].Size; + + ulong asciiPasswordDataPosition = context.Request.SendBuff[1].Position; + ulong asciiPasswordDataSize = context.Request.SendBuff[1].Size; + + byte[] asciiPasswordData = new byte[asciiPasswordDataSize]; + + context.Memory.Read(asciiPasswordDataPosition, asciiPasswordData); + + string asciiPassword = Encoding.ASCII.GetString(asciiPasswordData).Trim('\0'); + + context.ResponseData.Write(_clientCertificateId++); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { asciiPassword }); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs new file mode 100644 index 00000000..47d3eddb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs @@ -0,0 +1,251 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class SslManagedSocketConnection : ISslConnectionBase + { + public int SocketFd { get; } + + public ISocket Socket { get; } + + private BsdContext _bsdContext; + private SslVersion _sslVersion; + private SslStream _stream; + private bool _isBlockingSocket; + private int _previousReadTimeout; + + public SslManagedSocketConnection(BsdContext bsdContext, SslVersion sslVersion, int socketFd, ISocket socket) + { + _bsdContext = bsdContext; + _sslVersion = sslVersion; + + SocketFd = socketFd; + Socket = socket; + } + + private void StartSslOperation() + { + // Save blocking state + _isBlockingSocket = Socket.Blocking; + + // Force blocking for SslStream + Socket.Blocking = true; + } + + private void EndSslOperation() + { + // Restore blocking state + Socket.Blocking = _isBlockingSocket; + } + + private void StartSslReadOperation() + { + StartSslOperation(); + + if (!_isBlockingSocket) + { + _previousReadTimeout = _stream.ReadTimeout; + + _stream.ReadTimeout = 1; + } + } + + private void EndSslReadOperation() + { + if (!_isBlockingSocket) + { + _stream.ReadTimeout = _previousReadTimeout; + } + + EndSslOperation(); + } + +// NOTE: We silence warnings about TLS 1.0 and 1.1 as games will likely use it. +#pragma warning disable SYSLIB0039 + private static SslProtocols TranslateSslVersion(SslVersion version) + { + switch (version & SslVersion.VersionMask) + { + case SslVersion.Auto: + return SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; + case SslVersion.TlsV10: + return SslProtocols.Tls; + case SslVersion.TlsV11: + return SslProtocols.Tls11; + case SslVersion.TlsV12: + return SslProtocols.Tls12; + case SslVersion.TlsV13: + return SslProtocols.Tls13; + default: + throw new NotImplementedException(version.ToString()); + } + } +#pragma warning restore SYSLIB0039 + + public ResultCode Handshake(string hostName) + { + StartSslOperation(); + _stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null); + _stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false); + EndSslOperation(); + + return ResultCode.Success; + } + + public ResultCode Peek(out int peekCount, Memory<byte> buffer) + { + // NOTE: We cannot support that on .NET SSL API. + // As Nintendo's curl implementation detail check if a connection is alive via Peek, we just return that it would block to let it know that it's alive. + peekCount = -1; + + return ResultCode.WouldBlock; + } + + public int Pending() + { + // Unsupported + return 0; + } + + private static bool TryTranslateWinSockError(bool isBlocking, WsaError error, out ResultCode resultCode) + { + switch (error) + { + case WsaError.WSAETIMEDOUT: + resultCode = isBlocking ? ResultCode.Timeout : ResultCode.WouldBlock; + return true; + case WsaError.WSAECONNABORTED: + resultCode = ResultCode.ConnectionAbort; + return true; + case WsaError.WSAECONNRESET: + resultCode = ResultCode.ConnectionReset; + return true; + default: + resultCode = ResultCode.Success; + return false; + } + } + + public ResultCode Read(out int readCount, Memory<byte> buffer) + { + if (!Socket.Poll(0, SelectMode.SelectRead)) + { + readCount = -1; + + return ResultCode.WouldBlock; + } + + StartSslReadOperation(); + + try + { + readCount = _stream.Read(buffer.Span); + } + catch (IOException exception) + { + readCount = -1; + + if (exception.InnerException is SocketException socketException) + { + WsaError socketErrorCode = (WsaError)socketException.SocketErrorCode; + + if (TryTranslateWinSockError(_isBlockingSocket, socketErrorCode, out ResultCode result)) + { + return result; + } + else + { + throw socketException; + } + } + else + { + throw exception; + } + } + finally + { + EndSslReadOperation(); + } + + return ResultCode.Success; + } + + public ResultCode Write(out int writtenCount, ReadOnlyMemory<byte> buffer) + { + if (!Socket.Poll(0, SelectMode.SelectWrite)) + { + writtenCount = 0; + + return ResultCode.WouldBlock; + } + + StartSslOperation(); + + try + { + _stream.Write(buffer.Span); + } + catch (IOException exception) + { + writtenCount = -1; + + if (exception.InnerException is SocketException socketException) + { + WsaError socketErrorCode = (WsaError)socketException.SocketErrorCode; + + if (TryTranslateWinSockError(_isBlockingSocket, socketErrorCode, out ResultCode result)) + { + return result; + } + else + { + throw socketException; + } + } + else + { + throw exception; + } + } + finally + { + EndSslOperation(); + } + + // .NET API doesn't provide the size written, assume all written. + writtenCount = buffer.Length; + + return ResultCode.Success; + } + + public ResultCode GetServerCertificate(string hostname, Span<byte> certificates, out uint storageSize, out uint certificateCount) + { + byte[] rawCertData = _stream.RemoteCertificate.GetRawCertData(); + + storageSize = (uint)rawCertData.Length; + certificateCount = 1; + + if (rawCertData.Length > certificates.Length) + { + return ResultCode.CertBufferTooSmall; + } + + rawCertData.CopyTo(certificates); + + return ResultCode.Success; + } + + public void Dispose() + { + _bsdContext.CloseFileDescriptor(SocketFd); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs new file mode 100644 index 00000000..313220e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + struct BuiltInCertificateInfo + { + public CaCertificateId Id; + public TrustedCertStatus Status; + public ulong CertificateDataSize; + public ulong CertificateDataOffset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs new file mode 100644 index 00000000..5c84579a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum CaCertificateId : uint + { + // Nintendo CAs + NintendoCAG3 = 1, + NintendoClass2CAG3, + + // External CAs + AmazonRootCA1 = 1000, + StarfieldServicesRootCertificateAuthorityG2, + AddTrustExternalCARoot, + COMODOCertificationAuthority, + UTNDATACorpSGC, + UTNUSERFirstHardware, + BaltimoreCyberTrustRoot, + CybertrustGlobalRoot, + VerizonGlobalRootCA, + DigiCertAssuredIDRootCA, + DigiCertAssuredIDRootG2, + DigiCertGlobalRootCA, + DigiCertGlobalRootG2, + DigiCertHighAssuranceEVRootCA, + EntrustnetCertificationAuthority2048, + EntrustRootCertificationAuthority, + EntrustRootCertificationAuthorityG2, + GeoTrustGlobalCA2, + GeoTrustGlobalCA, + GeoTrustPrimaryCertificationAuthorityG3, + GeoTrustPrimaryCertificationAuthority, + GlobalSignRootCA, + GlobalSignRootCAR2, + GlobalSignRootCAR3, + GoDaddyClass2CertificationAuthority, + GoDaddyRootCertificateAuthorityG2, + StarfieldClass2CertificationAuthority, + StarfieldRootCertificateAuthorityG2, + ThawtePrimaryRootCAG3, + ThawtePrimaryRootCA, + VeriSignClass3PublicPrimaryCertificationAuthorityG3, + VeriSignClass3PublicPrimaryCertificationAuthorityG5, + VeriSignUniversalRootCertificationAuthority, + DSTRootCAX3, + USERTrustRSACertificationAuthority, + ISRGRootX10, + USERTrustECCCertificationAuthority, + COMODORSACertificationAuthority, + COMODOECCCertificationAuthority, + AmazonRootCA2, + AmazonRootCA3, + AmazonRootCA4, + DigiCertAssuredIDRootG3, + DigiCertGlobalRootG3, + DigiCertTrustedRootG4, + EntrustRootCertificationAuthorityEC1, + EntrustRootCertificationAuthorityG4, + GlobalSignECCRootCAR4, + GlobalSignECCRootCAR5, + GlobalSignECCRootCAR6, + GTSRootR1, + GTSRootR2, + GTSRootR3, + GTSRootR4, + SecurityCommunicationRootCA, + + All = uint.MaxValue + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs new file mode 100644 index 00000000..1d80f739 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum CertificateFormat : uint + { + Pem = 1, + Der = 2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs new file mode 100644 index 00000000..1cd06d6d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum IoMode : uint + { + Blocking = 1, + NonBlocking = 2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs new file mode 100644 index 00000000..3673200a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum OptionType : uint + { + DoNotCloseSocket, + GetServerCertChain, // 3.0.0+ + SkipDefaultVerify, // 5.0.0+ + EnableAlpn // 9.0.0+ + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs new file mode 100644 index 00000000..cec7b745 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum SessionCacheMode : uint + { + None, + SessionId, + SessionTicket + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs new file mode 100644 index 00000000..7110fd85 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + [Flags] + enum SslVersion : uint + { + Auto = 1 << 0, + TlsV10 = 1 << 3, + TlsV11 = 1 << 4, + TlsV12 = 1 << 5, + TlsV13 = 1 << 6, // 11.0.0+ + + VersionMask = 0xFFFFFF + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs new file mode 100644 index 00000000..7fd5efd6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum TrustedCertStatus : uint + { + Removed, + EnabledTrusted, + EnabledNotTrusted, + Revoked, + + Invalid = uint.MaxValue + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs new file mode 100644 index 00000000..d25bb6c3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + [Flags] + enum VerifyOption : uint + { + PeerCa = 1 << 0, + HostName = 1 << 1, + DateCheck = 1 << 2, + EvCertPartial = 1 << 3, + EvPolicyOid = 1 << 4, // 6.0.0+ + EvCertFingerprint = 1 << 5 // 6.0.0+ + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs new file mode 100644 index 00000000..3b33bf8b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs @@ -0,0 +1,95 @@ +using Ryujinx.Graphics.Gpu; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferItemConsumer : ConsumerBase + { + private GpuContext _gpuContext; + + public BufferItemConsumer(Switch device, + BufferQueueConsumer consumer, + uint consumerUsage, + int bufferCount, + bool controlledByApp, + IConsumerListener listener = null) : base(consumer, controlledByApp, listener) + { + _gpuContext = device.Gpu; + + Status status = Consumer.SetConsumerUsageBits(consumerUsage); + + if (status != Status.Success) + { + throw new InvalidOperationException(); + } + + if (bufferCount != -1) + { + status = Consumer.SetMaxAcquiredBufferCount(bufferCount); + + if (status != Status.Success) + { + throw new InvalidOperationException(); + } + } + } + + public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent, bool waitForFence = false) + { + lock (Lock) + { + Status status = AcquireBufferLocked(out BufferItem tmp, expectedPresent); + + if (status != Status.Success) + { + bufferItem = null; + + return status; + } + + // Make sure to clone the object to not temper the real instance. + bufferItem = (BufferItem)tmp.Clone(); + + if (waitForFence) + { + bufferItem.Fence.WaitForever(_gpuContext); + } + + bufferItem.GraphicBuffer.Set(Slots[bufferItem.Slot].GraphicBuffer); + + return Status.Success; + } + } + + public Status ReleaseBuffer(BufferItem bufferItem, ref AndroidFence fence) + { + lock (Lock) + { + Status result = AddReleaseFenceLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer, ref fence); + + if (result == Status.Success) + { + result = ReleaseBufferLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer); + } + + return result; + } + } + + public Status SetDefaultBufferSize(uint width, uint height) + { + lock (Lock) + { + return Consumer.SetDefaultBufferSize(width, height); + } + } + + public Status SetDefaultBufferFormat(PixelFormat defaultFormat) + { + lock (Lock) + { + return Consumer.SetDefaultBufferFormat(defaultFormat); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs new file mode 100644 index 00000000..bc0901ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + static class BufferQueue + { + public static BufferQueueCore CreateBufferQueue(Switch device, ulong pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer) + { + BufferQueueCore core = new BufferQueueCore(device, pid); + + producer = new BufferQueueProducer(core, device.System.TickSource); + consumer = new BufferQueueConsumer(core); + + return core; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs new file mode 100644 index 00000000..c9bb0a65 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs @@ -0,0 +1,420 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueConsumer + { + public BufferQueueCore Core { get; } + + public BufferQueueConsumer(BufferQueueCore core) + { + Core = core; + } + + public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + for (int i = 0; i < Core.MaxBufferCountCached; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + } + + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount) + { + bufferItem = null; + + Logger.Debug?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (Core.Queue.Count == 0) + { + bufferItem = null; + + return Status.NoBufferAvailaible; + } + + if (expectedPresent != 0) + { + // TODO: support this for advanced presenting. + throw new NotImplementedException(); + } + + bufferItem = Core.Queue[0]; + + if (Core.StillTracking(ref bufferItem)) + { + Core.Slots[bufferItem.Slot].AcquireCalled = true; + Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true; + Core.Slots[bufferItem.Slot].BufferState = BufferState.Acquired; + Core.Slots[bufferItem.Slot].Fence = AndroidFence.NoFence; + + ulong targetFrameNumber = Core.Slots[bufferItem.Slot].FrameNumber; + + for (int i = 0; i < Core.BufferHistory.Length; i++) + { + if (Core.BufferHistory[i].FrameNumber == targetFrameNumber) + { + Core.BufferHistory[i].State = BufferState.Acquired; + + break; + } + } + } + + if (bufferItem.AcquireCalled) + { + bufferItem.GraphicBuffer.Reset(); + } + + Core.Queue.RemoveAt(0); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByConsumerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public Status AttachBuffer(out int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + int freeSlot = BufferSlotArray.InvalidBufferSlot; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + else if (Core.Slots[i].BufferState == BufferState.Free) + { + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[i].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = i; + } + } + } + + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount + 1) + { + slot = BufferSlotArray.InvalidBufferSlot; + + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (freeSlot == BufferSlotArray.InvalidBufferSlot) + { + slot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoMemory; + } + + Core.UpdateMaxBufferCountCachedLocked(freeSlot); + + slot = freeSlot; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Acquired; + Core.Slots[slot].AttachedByConsumer = true; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].AcquireCalled = false; + } + + return Status.Success; + } + + public Status ReleaseBuffer(int slot, ulong frameNumber, ref AndroidFence fence) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + IProducerListener listener = null; + + lock (Core.Lock) + { + if (Core.Slots[slot].FrameNumber != frameNumber) + { + return Status.StaleBufferSlot; + } + + foreach (BufferItem item in Core.Queue) + { + if (item.Slot == slot) + { + return Status.BadValue; + } + } + + if (Core.Slots[slot].BufferState == BufferState.Acquired) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = fence; + + listener = Core.ProducerListener; + } + else if (Core.Slots[slot].NeedsCleanupOnRelease) + { + Core.Slots[slot].NeedsCleanupOnRelease = false; + + return Status.StaleBufferSlot; + } + else + { + return Status.BadValue; + } + + Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + listener?.OnBufferReleased(); + + return Status.Success; + } + + public Status Connect(IConsumerListener consumerListener, bool controlledByApp) + { + if (consumerListener == null) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + Core.ConsumerListener = consumerListener; + Core.ConsumerControlledByApp = controlledByApp; + } + + return Status.Success; + } + + public Status Disconnect() + { + lock (Core.Lock) + { + if (!Core.IsConsumerConnectedLocked()) + { + return Status.BadValue; + } + + Core.IsAbandoned = true; + Core.ConsumerListener = null; + + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status GetReleasedBuffers(out ulong slotMask) + { + slotMask = 0; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (!Core.Slots[slot].AcquireCalled) + { + slotMask |= 1UL << slot; + } + } + + for (int i = 0; i < Core.Queue.Count; i++) + { + if (Core.Queue[i].AcquireCalled) + { + slotMask &= ~(1UL << i); + } + } + } + + return Status.Success; + } + + public Status SetDefaultBufferSize(uint width, uint height) + { + if (width == 0 || height == 0) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + Core.DefaultWidth = (int)width; + Core.DefaultHeight = (int)height; + } + + return Status.Success; + } + + public Status SetDefaultMaxBufferCount(int bufferMaxCount) + { + lock (Core.Lock) + { + return Core.SetDefaultMaxBufferCountLocked(bufferMaxCount); + } + } + + public Status DisableAsyncBuffer() + { + lock (Core.Lock) + { + if (Core.IsConsumerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.UseAsyncBuffer = false; + } + + return Status.Success; + } + + public Status SetMaxAcquiredBufferCount(int maxAcquiredBufferCount) + { + if (maxAcquiredBufferCount < 0 || maxAcquiredBufferCount > BufferSlotArray.MaxAcquiredBuffers) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsProducerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.MaxAcquiredBufferCount = maxAcquiredBufferCount; + } + + return Status.Success; + } + + public Status SetDefaultBufferFormat(PixelFormat defaultFormat) + { + lock (Core.Lock) + { + Core.DefaultBufferFormat = defaultFormat; + } + + return Status.Success; + } + + public Status SetConsumerUsageBits(uint usage) + { + lock (Core.Lock) + { + Core.ConsumerUsageBits = usage; + } + + return Status.Success; + } + + public Status SetTransformHint(NativeWindowTransform transformHint) + { + lock (Core.Lock) + { + Core.TransformHint = transformHint; + } + + return Status.Success; + } + + public Status SetPresentTime(int slot, ulong frameNumber, TimeSpanType presentationTime) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.Slots[slot].FrameNumber != frameNumber) + { + return Status.StaleBufferSlot; + } + + if (Core.Slots[slot].PresentationTime.NanoSeconds == 0) + { + Core.Slots[slot].PresentationTime = presentationTime; + } + + for (int i = 0; i < Core.BufferHistory.Length; i++) + { + if (Core.BufferHistory[i].FrameNumber == frameNumber) + { + Core.BufferHistory[i].PresentationTime = presentationTime; + + break; + } + } + } + + return Status.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs new file mode 100644 index 00000000..1efd37f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -0,0 +1,341 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueCore + { + public BufferSlotArray Slots; + public int OverrideMaxBufferCount; + public bool UseAsyncBuffer; + public bool DequeueBufferCannotBlock; + public PixelFormat DefaultBufferFormat; + public int DefaultWidth; + public int DefaultHeight; + public int DefaultMaxBufferCount; + public int MaxAcquiredBufferCount; + public bool BufferHasBeenQueued; + public ulong FrameCounter; + public NativeWindowTransform TransformHint; + public bool IsAbandoned; + public NativeWindowApi ConnectedApi; + public bool IsAllocating; + public IProducerListener ProducerListener; + public IConsumerListener ConsumerListener; + public bool ConsumerControlledByApp; + public uint ConsumerUsageBits; + public List<BufferItem> Queue; + public BufferInfo[] BufferHistory; + public uint BufferHistoryPosition; + public bool EnableExternalEvent; + public int MaxBufferCountCached; + + public readonly object Lock = new object(); + + private KEvent _waitBufferFreeEvent; + private KEvent _frameAvailableEvent; + + public ulong Owner { get; } + + public bool Active { get; private set; } + + public const int BufferHistoryArraySize = 8; + + public event Action BufferQueued; + + public BufferQueueCore(Switch device, ulong pid) + { + Slots = new BufferSlotArray(); + IsAbandoned = false; + OverrideMaxBufferCount = 0; + DequeueBufferCannotBlock = false; + UseAsyncBuffer = false; + DefaultWidth = 1; + DefaultHeight = 1; + DefaultMaxBufferCount = 2; + MaxAcquiredBufferCount = 1; + FrameCounter = 0; + TransformHint = 0; + DefaultBufferFormat = PixelFormat.Rgba8888; + IsAllocating = false; + ProducerListener = null; + ConsumerListener = null; + ConsumerUsageBits = 0; + + Queue = new List<BufferItem>(); + + // TODO: CreateGraphicBufferAlloc? + + _waitBufferFreeEvent = new KEvent(device.System.KernelContext); + _frameAvailableEvent = new KEvent(device.System.KernelContext); + + Owner = pid; + + Active = true; + + BufferHistory = new BufferInfo[BufferHistoryArraySize]; + EnableExternalEvent = true; + MaxBufferCountCached = 0; + } + + public int GetMinUndequeuedBufferCountLocked(bool async) + { + if (!UseAsyncBuffer) + { + return 0; + } + + if (DequeueBufferCannotBlock || async) + { + return MaxAcquiredBufferCount + 1; + } + + return MaxAcquiredBufferCount; + } + + public int GetMinMaxBufferCountLocked(bool async) + { + return GetMinUndequeuedBufferCountLocked(async); + } + + public void UpdateMaxBufferCountCachedLocked(int slot) + { + if (MaxBufferCountCached <= slot) + { + MaxBufferCountCached = slot + 1; + } + } + + public int GetMaxBufferCountLocked(bool async) + { + int minMaxBufferCount = GetMinMaxBufferCountLocked(async); + + int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount); + + if (OverrideMaxBufferCount != 0) + { + return OverrideMaxBufferCount; + } + + // Preserve all buffers already in control of the producer and the consumer. + for (int slot = maxBufferCount; slot < Slots.Length; slot++) + { + BufferState state = Slots[slot].BufferState; + + if (state == BufferState.Queued || state == BufferState.Dequeued) + { + maxBufferCount = slot + 1; + } + } + + return maxBufferCount; + } + + public Status SetDefaultMaxBufferCountLocked(int count) + { + int minBufferCount = UseAsyncBuffer ? 2 : 1; + + if (count < minBufferCount || count > Slots.Length) + { + return Status.BadValue; + } + + DefaultMaxBufferCount = count; + + SignalDequeueEvent(); + + return Status.Success; + } + + public void SignalWaitBufferFreeEvent() + { + if (EnableExternalEvent) + { + _waitBufferFreeEvent.WritableEvent.Signal(); + } + } + + public void SignalFrameAvailableEvent() + { + if (EnableExternalEvent) + { + _frameAvailableEvent.WritableEvent.Signal(); + } + } + + public void PrepareForExit() + { + lock (Lock) + { + Active = false; + + Monitor.PulseAll(Lock); + } + } + + // TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases. + public void SignalDequeueEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitDequeueEvent() + { + WaitForLock(); + } + + public void SignalIsAllocatingEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitIsAllocatingEvent() + { + WaitForLock(); + } + + public void SignalQueueEvent() + { + BufferQueued?.Invoke(); + } + + private void WaitForLock() + { + if (Active) + { + Monitor.Wait(Lock); + } + } + + public void FreeBufferLocked(int slot) + { + Slots[slot].GraphicBuffer.Reset(); + + if (Slots[slot].BufferState == BufferState.Acquired) + { + Slots[slot].NeedsCleanupOnRelease = true; + } + + Slots[slot].BufferState = BufferState.Free; + Slots[slot].FrameNumber = uint.MaxValue; + Slots[slot].AcquireCalled = false; + Slots[slot].Fence.FenceCount = 0; + } + + public void FreeAllBuffersLocked() + { + BufferHasBeenQueued = false; + + for (int slot = 0; slot < Slots.Length; slot++) + { + FreeBufferLocked(slot); + } + } + + public bool StillTracking(ref BufferItem item) + { + BufferSlot slot = Slots[item.Slot]; + + // TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be. + return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + } + + public void WaitWhileAllocatingLocked() + { + while (IsAllocating) + { + WaitIsAllocatingEvent(); + } + } + + public void CheckSystemEventsLocked(int maxBufferCount) + { + if (!EnableExternalEvent) + { + return; + } + + bool needBufferReleaseSignal = false; + bool needFrameAvailableSignal = false; + + if (maxBufferCount > 1) + { + for (int i = 0; i < maxBufferCount; i++) + { + if (Slots[i].BufferState == BufferState.Queued) + { + needFrameAvailableSignal = true; + } + else if (Slots[i].BufferState == BufferState.Free) + { + needBufferReleaseSignal = true; + } + } + } + + if (needBufferReleaseSignal) + { + SignalWaitBufferFreeEvent(); + } + else + { + _waitBufferFreeEvent.WritableEvent.Clear(); + } + + if (needFrameAvailableSignal) + { + SignalFrameAvailableEvent(); + } + else + { + _frameAvailableEvent.WritableEvent.Clear(); + } + } + + public bool IsProducerConnectedLocked() + { + return ConnectedApi != NativeWindowApi.NoApi; + } + + public bool IsConsumerConnectedLocked() + { + return ConsumerListener != null; + } + + public KReadableEvent GetWaitBufferFreeEvent() + { + lock (Lock) + { + return _waitBufferFreeEvent.ReadableEvent; + } + } + + public bool IsOwnedByConsumerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Acquired) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + + public bool IsOwnedByProducerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Dequeued) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs new file mode 100644 index 00000000..833bc26e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs @@ -0,0 +1,871 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueProducer : IGraphicBufferProducer + { + public BufferQueueCore Core { get; } + + private readonly ITickSource _tickSource; + + private uint _stickyTransform; + + private uint _nextCallbackTicket; + private uint _currentCallbackTicket; + private uint _callbackTicket; + + private readonly object _callbackLock = new object(); + + public BufferQueueProducer(BufferQueueCore core, ITickSource tickSource) + { + Core = core; + _tickSource = tickSource; + + _stickyTransform = 0; + _callbackTicket = 0; + _nextCallbackTicket = 0; + _currentCallbackTicket = 0; + } + + public override Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + graphicBuffer = new AndroidStrongPointer<GraphicBuffer>(); + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + graphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + + Core.Slots[slot].RequestBufferCalled = true; + + return Status.Success; + } + } + + public override Status SetBufferCount(int bufferCount) + { + IConsumerListener listener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (bufferCount > BufferSlotArray.NumBufferSlots) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Dequeued) + { + return Status.BadValue; + } + } + + if (bufferCount == 0) + { + Core.OverrideMaxBufferCount = 0; + Core.SignalDequeueEvent(); + + return Status.Success; + } + + int minBufferSlots = Core.GetMinMaxBufferCountLocked(false); + + if (bufferCount < minBufferSlots) + { + return Status.BadValue; + } + + int preallocatedBufferCount = GetPreallocatedBufferCountLocked(); + + if (preallocatedBufferCount <= 0) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + } + else if (preallocatedBufferCount < bufferCount) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "Not enough buffers. Try with more pre-allocated buffers"); + + return Status.Success; + } + + Core.OverrideMaxBufferCount = bufferCount; + + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + + listener = Core.ConsumerListener; + } + + listener?.OnBuffersReleased(); + + return Status.Success; + } + + public override Status DequeueBuffer(out int slot, + out AndroidFence fence, + bool async, + uint width, + uint height, + PixelFormat format, + uint usage) + { + if ((width == 0 && height != 0) || (height == 0 && width != 0)) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.BadValue; + } + + Status returnFlags = Status.Success; + + bool attachedByConsumer = false; + + lock (Core.Lock) + { + if (format == PixelFormat.Unknown) + { + format = Core.DefaultBufferFormat; + } + + usage |= Core.ConsumerUsageBits; + + Status status = WaitForFreeSlotThenRelock(async, out slot, out returnFlags); + + if (status != Status.Success) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + fence = AndroidFence.NoFence; + + Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + attachedByConsumer = Core.Slots[slot].AttachedByConsumer; + + if (width == 0 || height == 0) + { + width = (uint)Core.DefaultWidth; + height = (uint)Core.DefaultHeight; + } + + GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object; + + if (Core.Slots[slot].GraphicBuffer.IsNull + || graphicBuffer.Width != width + || graphicBuffer.Height != height + || graphicBuffer.Format != format + || (graphicBuffer.Usage & usage) != usage) + { + if (!Core.Slots[slot].IsPreallocated) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoMemory; + } + else + { + Logger.Error?.Print(LogClass.SurfaceFlinger, + $"Preallocated buffer mismatch - slot {slot}\n" + + $"available: Width = {graphicBuffer.Width} Height = {graphicBuffer.Height} Format = {graphicBuffer.Format} Usage = {graphicBuffer.Usage:x} " + + $"requested: Width = {width} Height = {height} Format = {format} Usage = {usage:x}"); + + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoInit; + } + } + + Core.Slots[slot].BufferState = BufferState.Dequeued; + + Core.UpdateMaxBufferCountCachedLocked(slot); + + fence = Core.Slots[slot].Fence; + + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].QueueTime = TimeSpanType.Zero; + Core.Slots[slot].PresentationTime = TimeSpanType.Zero; + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async)); + } + + if (attachedByConsumer) + { + returnFlags |= Status.BufferNeedsReallocation; + } + + return returnFlags; + } + + public override Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public override Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence) + { + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + if (Core.IsAbandoned) + { + graphicBuffer = default; + fence = AndroidFence.NoFence; + + return Status.NoInit; + } + + int nextBufferSlot = BufferSlotArray.InvalidBufferSlot; + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull) + { + if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[nextBufferSlot].FrameNumber) + { + nextBufferSlot = slot; + } + } + } + + if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot) + { + graphicBuffer = default; + fence = AndroidFence.NoFence; + + return Status.NoMemory; + } + + graphicBuffer = Core.Slots[nextBufferSlot].GraphicBuffer; + fence = Core.Slots[nextBufferSlot].Fence; + + Core.FreeBufferLocked(nextBufferSlot); + + return Status.Success; + } + } + + public override Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags); + + if (status != Status.Success) + { + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + Core.UpdateMaxBufferCountCachedLocked(slot); + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Dequeued; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = true; + + return returnFlags; + } + } + + public override Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output) + { + output = default; + + switch (input.ScalingMode) + { + case NativeWindowScalingMode.Freeze: + case NativeWindowScalingMode.ScaleToWindow: + case NativeWindowScalingMode.ScaleCrop: + case NativeWindowScalingMode.Unknown: + case NativeWindowScalingMode.NoScaleCrop: + break; + default: + return Status.BadValue; + } + + BufferItem item = new BufferItem(); + + IConsumerListener frameAvailableListener = null; + IConsumerListener frameReplaceListener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(input.Async != 0); + + if (input.Async != 0 && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + return Status.BadValue; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was queued without requesting a buffer"); + + return Status.BadValue; + } + + input.Crop.Intersect(Core.Slots[slot].GraphicBuffer.Object.ToRect(), out Rect croppedRect); + + if (croppedRect != input.Crop) + { + return Status.BadValue; + } + + Core.Slots[slot].Fence = input.Fence; + Core.Slots[slot].BufferState = BufferState.Queued; + Core.FrameCounter++; + Core.Slots[slot].FrameNumber = Core.FrameCounter; + Core.Slots[slot].QueueTime = TimeSpanType.FromTimeSpan(_tickSource.ElapsedTime); + Core.Slots[slot].PresentationTime = TimeSpanType.Zero; + + item.AcquireCalled = Core.Slots[slot].AcquireCalled; + item.Crop = input.Crop; + item.Transform = input.Transform; + item.TransformToDisplayInverse = (input.Transform & NativeWindowTransform.InverseDisplay) == NativeWindowTransform.InverseDisplay; + item.ScalingMode = input.ScalingMode; + item.Timestamp = input.Timestamp; + item.IsAutoTimestamp = input.IsAutoTimestamp != 0; + item.SwapInterval = input.SwapInterval; + item.FrameNumber = Core.FrameCounter; + item.Slot = slot; + item.Fence = input.Fence; + item.IsDroppable = Core.DequeueBufferCannotBlock || input.Async != 0; + + item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner); + + Core.BufferHistoryPosition = (Core.BufferHistoryPosition + 1) % BufferQueueCore.BufferHistoryArraySize; + + Core.BufferHistory[Core.BufferHistoryPosition] = new BufferInfo + { + FrameNumber = Core.FrameCounter, + QueueTime = Core.Slots[slot].QueueTime, + State = BufferState.Queued + }; + + _stickyTransform = input.StickyTransform; + + if (Core.Queue.Count == 0) + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + else + { + BufferItem frontItem = Core.Queue[0]; + + if (frontItem.IsDroppable) + { + if (Core.StillTracking(ref frontItem)) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + } + + Core.Queue.RemoveAt(0); + Core.Queue.Insert(0, item); + + frameReplaceListener = Core.ConsumerListener; + } + else + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + } + + Core.BufferHasBeenQueued = true; + Core.SignalDequeueEvent(); + + Core.CheckSystemEventsLocked(maxBufferCount); + + output = new QueueBufferOutput + { + Width = (uint)Core.DefaultWidth, + Height = (uint)Core.DefaultHeight, + TransformHint = Core.TransformHint, + NumPendingBuffers = (uint)Core.Queue.Count + }; + + if ((input.StickyTransform & 8) != 0) + { + output.TransformHint |= NativeWindowTransform.ReturnFrameNumber; + output.FrameNumber = Core.Slots[slot].FrameNumber; + } + + _callbackTicket = _nextCallbackTicket++; + } + + lock (_callbackLock) + { + while (_callbackTicket != _currentCallbackTicket) + { + Monitor.Wait(_callbackLock); + } + + frameAvailableListener?.OnFrameAvailable(ref item); + frameReplaceListener?.OnFrameReplaced(ref item); + + _currentCallbackTicket++; + + Monitor.PulseAll(_callbackLock); + } + + Core.SignalQueueEvent(); + + return Status.Success; + } + + public override void CancelBuffer(int slot, ref AndroidFence fence) + { + lock (Core.Lock) + { + if (Core.IsAbandoned || slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return; + } + + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].Fence = fence; + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + } + } + + public override Status Query(NativeWindowAttribute what, out int outValue) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + outValue = 0; + return Status.NoInit; + } + + switch (what) + { + case NativeWindowAttribute.Width: + outValue = Core.DefaultWidth; + return Status.Success; + case NativeWindowAttribute.Height: + outValue = Core.DefaultHeight; + return Status.Success; + case NativeWindowAttribute.Format: + outValue = (int)Core.DefaultBufferFormat; + return Status.Success; + case NativeWindowAttribute.MinUnqueuedBuffers: + outValue = Core.GetMinUndequeuedBufferCountLocked(false); + return Status.Success; + case NativeWindowAttribute.ConsumerRunningBehind: + outValue = Core.Queue.Count > 1 ? 1 : 0; + return Status.Success; + case NativeWindowAttribute.ConsumerUsageBits: + outValue = (int)Core.ConsumerUsageBits; + return Status.Success; + case NativeWindowAttribute.MaxBufferCountAsync: + outValue = Core.GetMaxBufferCountLocked(true); + return Status.Success; + default: + outValue = 0; + return Status.BadValue; + } + } + } + + public override Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output) + { + output = new QueueBufferOutput(); + + lock (Core.Lock) + { + if (Core.IsAbandoned || Core.ConsumerListener == null) + { + return Status.NoInit; + } + + if (Core.ConnectedApi != NativeWindowApi.NoApi) + { + return Status.BadValue; + } + + Core.BufferHasBeenQueued = false; + Core.DequeueBufferCannotBlock = Core.ConsumerControlledByApp && producerControlledByApp; + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + Core.ProducerListener = listener; + Core.ConnectedApi = api; + + output.Width = (uint)Core.DefaultWidth; + output.Height = (uint)Core.DefaultHeight; + output.TransformHint = Core.TransformHint; + output.NumPendingBuffers = (uint)Core.Queue.Count; + + if (NxSettings.Settings.TryGetValue("nv!nvn_no_vsync_capability", out object noVSyncCapability) && (bool)noVSyncCapability) + { + output.TransformHint |= NativeWindowTransform.NoVSyncCapability; + } + + return Status.Success; + default: + return Status.BadValue; + } + } + } + + public override Status Disconnect(NativeWindowApi api) + { + IProducerListener producerListener = null; + + Status status = Status.BadValue; + + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + if (Core.IsAbandoned) + { + return Status.Success; + } + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + if (Core.ConnectedApi == api) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + + producerListener = Core.ProducerListener; + + Core.ProducerListener = null; + Core.ConnectedApi = NativeWindowApi.NoApi; + + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + status = Status.Success; + } + break; + } + } + + producerListener?.OnBufferReleased(); + + return status; + } + + private int GetPreallocatedBufferCountLocked() + { + int bufferCount = 0; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].IsPreallocated) + { + bufferCount++; + } + } + + return bufferCount; + } + + public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = false; + Core.Slots[slot].AcquireCalled = false; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].IsPreallocated = !graphicBuffer.IsNull; + Core.Slots[slot].FrameNumber = 0; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + if (!Core.Slots[slot].GraphicBuffer.IsNull) + { + Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits; + } + + Core.OverrideMaxBufferCount = GetPreallocatedBufferCountLocked(); + Core.UseAsyncBuffer = false; + + if (!graphicBuffer.IsNull) + { + // NOTE: Nintendo set the default width, height and format from the GraphicBuffer.. + // This is entirely wrong and should only be controlled by the consumer... + Core.DefaultWidth = graphicBuffer.Object.Width; + Core.DefaultHeight = graphicBuffer.Object.Height; + Core.DefaultBufferFormat = graphicBuffer.Object.Format; + } + else + { + bool allBufferFreed = true; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (!Core.Slots[i].GraphicBuffer.IsNull) + { + allBufferFreed = false; + break; + } + } + + if (allBufferFreed) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + return Status.Success; + } + } + + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + + return Status.Success; + } + } + + private Status WaitForFreeSlotThenRelock(bool async, out int freeSlot, out Status returnStatus) + { + bool tryAgain = true; + + freeSlot = BufferSlotArray.InvalidBufferSlot; + returnStatus = Status.Success; + + while (tryAgain) + { + if (Core.IsAbandoned) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(async); + + if (async && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.BadValue; + } + + + if (maxBufferCount < Core.MaxBufferCountCached) + { + for (int slot = maxBufferCount; slot < Core.MaxBufferCountCached; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull && !Core.Slots[slot].IsPreallocated) + { + Core.FreeBufferLocked(slot); + returnStatus |= Status.ReleaseAllBuffers; + } + } + } + + freeSlot = BufferSlotArray.InvalidBufferSlot; + + int dequeuedCount = 0; + int acquiredCount = 0; + + for (int slot = 0; slot < maxBufferCount; slot++) + { + switch (Core.Slots[slot].BufferState) + { + case BufferState.Acquired: + acquiredCount++; + break; + case BufferState.Dequeued: + dequeuedCount++; + break; + case BufferState.Free: + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = slot; + } + break; + default: + break; + } + } + + // The producer SHOULD call SetBufferCount otherwise it's not allowed to dequeue multiple buffers. + if (Core.OverrideMaxBufferCount == 0 && dequeuedCount > 0) + { + return Status.InvalidOperation; + } + + if (Core.BufferHasBeenQueued) + { + int newUndequeuedCount = maxBufferCount - (dequeuedCount + 1); + int minUndequeuedCount = Core.GetMinUndequeuedBufferCountLocked(async); + + if (newUndequeuedCount < minUndequeuedCount) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Min undequeued buffer count ({minUndequeuedCount}) exceeded (dequeued = {dequeuedCount} undequeued = {newUndequeuedCount})"); + + return Status.InvalidOperation; + } + } + + bool tooManyBuffers = Core.Queue.Count > maxBufferCount; + + tryAgain = freeSlot == BufferSlotArray.InvalidBufferSlot || tooManyBuffers; + + if (tryAgain) + { + if (async || (Core.DequeueBufferCannotBlock && acquiredCount < Core.MaxAcquiredBufferCount)) + { + Core.CheckSystemEventsLocked(maxBufferCount); + + return Status.WouldBlock; + } + + Core.WaitDequeueEvent(); + + if (!Core.Active) + { + break; + } + } + } + + return Status.Success; + } + + protected override KReadableEvent GetWaitBufferFreeEvent() + { + return Core.GetWaitBufferFreeEvent(); + } + + public override Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos) + { + if (bufferHistoryCount <= 0) + { + bufferInfos = Span<BufferInfo>.Empty; + + return Status.BadValue; + } + + lock (Core.Lock) + { + bufferHistoryCount = Math.Min(bufferHistoryCount, Core.BufferHistory.Length); + + BufferInfo[] result = new BufferInfo[bufferHistoryCount]; + + uint position = Core.BufferHistoryPosition; + + for (uint i = 0; i < bufferHistoryCount; i++) + { + result[i] = Core.BufferHistory[(position - i) % Core.BufferHistory.Length]; + + position--; + } + + bufferInfos = result; + + return Status.Success; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs new file mode 100644 index 00000000..fb84934a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferSlot + { + public AndroidStrongPointer<GraphicBuffer> GraphicBuffer; + public BufferState BufferState; + public bool RequestBufferCalled; + public ulong FrameNumber; + public AndroidFence Fence; + public bool AcquireCalled; + public bool NeedsCleanupOnRelease; + public bool AttachedByConsumer; + public TimeSpanType QueueTime; + public TimeSpanType PresentationTime; + public bool IsPreallocated; + + public BufferSlot() + { + GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>(); + BufferState = BufferState.Free; + QueueTime = TimeSpanType.Zero; + PresentationTime = TimeSpanType.Zero; + IsPreallocated = false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs new file mode 100644 index 00000000..d2404c58 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferSlotArray + { + // TODO: move to BufferQueue + public const int NumBufferSlots = 0x40; + public const int MaxAcquiredBuffers = NumBufferSlots - 2; + public const int InvalidBufferSlot = -1; + + private BufferSlot[] _raw = new BufferSlot[NumBufferSlots]; + + public BufferSlotArray() + { + for (int i = 0; i < _raw.Length; i++) + { + _raw[i] = new BufferSlot(); + } + } + + public BufferSlot this[int index] + { + get => _raw[index]; + set => _raw[index] = value; + } + + public int Length => NumBufferSlots; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs new file mode 100644 index 00000000..49fceed9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs @@ -0,0 +1,175 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class ConsumerBase : IConsumerListener + { + public class Slot + { + public AndroidStrongPointer<GraphicBuffer> GraphicBuffer; + public AndroidFence Fence; + public ulong FrameNumber; + + public Slot() + { + GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>(); + } + } + + protected Slot[] Slots = new Slot[BufferSlotArray.NumBufferSlots]; + + protected bool IsAbandoned; + + protected BufferQueueConsumer Consumer; + + protected readonly object Lock = new object(); + + private IConsumerListener _listener; + + public ConsumerBase(BufferQueueConsumer consumer, bool controlledByApp, IConsumerListener listener) + { + for (int i = 0; i < Slots.Length; i++) + { + Slots[i] = new Slot(); + } + + IsAbandoned = false; + Consumer = consumer; + _listener = listener; + + Status connectStatus = consumer.Connect(this, controlledByApp); + + if (connectStatus != Status.Success) + { + throw new InvalidOperationException(); + } + } + + public virtual void OnBuffersReleased() + { + lock (Lock) + { + if (IsAbandoned) + { + return; + } + + Consumer.GetReleasedBuffers(out ulong slotMask); + + for (int i = 0; i < Slots.Length; i++) + { + if ((slotMask & (1UL << i)) != 0) + { + FreeBufferLocked(i); + } + } + } + } + + public virtual void OnFrameAvailable(ref BufferItem item) + { + _listener?.OnFrameAvailable(ref item); + } + + public virtual void OnFrameReplaced(ref BufferItem item) + { + _listener?.OnFrameReplaced(ref item); + } + + protected virtual void FreeBufferLocked(int slotIndex) + { + Slots[slotIndex].GraphicBuffer.Reset(); + + Slots[slotIndex].Fence = AndroidFence.NoFence; + Slots[slotIndex].FrameNumber = 0; + } + + public void Abandon() + { + lock (Lock) + { + if (!IsAbandoned) + { + AbandonLocked(); + + IsAbandoned = true; + } + } + } + + protected virtual void AbandonLocked() + { + for (int i = 0; i < Slots.Length; i++) + { + FreeBufferLocked(i); + } + + Consumer.Disconnect(); + } + + protected virtual Status AcquireBufferLocked(out BufferItem bufferItem, ulong expectedPresent) + { + Status acquireStatus = Consumer.AcquireBuffer(out bufferItem, expectedPresent); + + if (acquireStatus != Status.Success) + { + return acquireStatus; + } + + if (!bufferItem.GraphicBuffer.IsNull) + { + Slots[bufferItem.Slot].GraphicBuffer.Set(bufferItem.GraphicBuffer.Object); + } + + Slots[bufferItem.Slot].FrameNumber = bufferItem.FrameNumber; + Slots[bufferItem.Slot].Fence = bufferItem.Fence; + + return Status.Success; + } + + protected virtual Status AddReleaseFenceLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer, ref AndroidFence fence) + { + if (!StillTracking(slot, ref graphicBuffer)) + { + return Status.Success; + } + + Slots[slot].Fence = fence; + + return Status.Success; + } + + protected virtual Status ReleaseBufferLocked(int slot, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + if (!StillTracking(slot, ref graphicBuffer)) + { + return Status.Success; + } + + Status result = Consumer.ReleaseBuffer(slot, Slots[slot].FrameNumber, ref Slots[slot].Fence); + + if (result == Status.StaleBufferSlot) + { + FreeBufferLocked(slot); + } + + Slots[slot].Fence = AndroidFence.NoFence; + + return result; + } + + protected virtual bool StillTracking(int slotIndex, ref AndroidStrongPointer<GraphicBuffer> graphicBuffer) + { + if (slotIndex < 0 || slotIndex >= Slots.Length) + { + return false; + } + + Slot slot = Slots[slotIndex]; + + // TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be. + return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == graphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs new file mode 100644 index 00000000..d6c98be1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class HOSBinderDriverServer : IHOSBinderDriver + { + private static Dictionary<int, IBinder> _registeredBinderObjects = new Dictionary<int, IBinder>(); + + private static int _lastBinderId = 0; + + private static object _lock = new object(); + + public static int RegisterBinderObject(IBinder binder) + { + lock (_lock) + { + _lastBinderId++; + + _registeredBinderObjects.Add(_lastBinderId, binder); + + return _lastBinderId; + } + } + + public static void UnregisterBinderObject(int binderId) + { + lock (_lock) + { + _registeredBinderObjects.Remove(binderId); + } + } + + public static int GetBinderId(IBinder binder) + { + lock (_lock) + { + foreach (KeyValuePair<int, IBinder> pair in _registeredBinderObjects) + { + if (ReferenceEquals(binder, pair.Value)) + { + return pair.Key; + } + } + + return -1; + } + } + + private static IBinder GetBinderObjectById(int binderId) + { + lock (_lock) + { + if (_registeredBinderObjects.TryGetValue(binderId, out IBinder binder)) + { + return binder; + } + + return null; + } + } + + protected override ResultCode AdjustRefcount(int binderId, int addVal, int type) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.AdjustRefcount(addVal, type); + } + + protected override void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + readableEvent = null; + + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return; + } + + binder.GetNativeHandle(typeId, out readableEvent); + } + + protected override ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.OnTransact(code, flags, inputParcel, outputParcel); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs new file mode 100644 index 00000000..9003201b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs @@ -0,0 +1,41 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IBinder + { + ResultCode AdjustRefcount(int addVal, int type); + + void GetNativeHandle(uint typeId, out KReadableEvent readableEvent); + + ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel) + { + Parcel inputParcelReader = new Parcel(inputParcel.ToArray()); + + // TODO: support objects? + Parcel outputParcelWriter = new Parcel((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0); + + string inputInterfaceToken = inputParcelReader.ReadInterfaceToken(); + + if (!InterfaceToken.Equals(inputInterfaceToken)) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid interface token {inputInterfaceToken} (expected: {InterfaceToken}"); + + return ResultCode.Success; + } + + OnTransact(code, flags, inputParcelReader, outputParcelWriter); + + outputParcelWriter.Finish().CopyTo(outputParcel); + + return ResultCode.Success; + } + + void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel); + + string InterfaceToken { get; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs new file mode 100644 index 00000000..78f9c2e7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IConsumerListener + { + void OnFrameAvailable(ref BufferItem item); + void OnFrameReplaced(ref BufferItem item); + void OnBuffersReleased(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs new file mode 100644 index 00000000..bfb76952 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IFlattenable + { + uint GetFlattenedSize(); + + uint GetFdCount(); + + void Flatten(Parcel parcel); + + void Unflatten(Parcel parcel); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs new file mode 100644 index 00000000..f0b393a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs @@ -0,0 +1,304 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IGraphicBufferProducer : IBinder + { + public string InterfaceToken => "android.gui.IGraphicBufferProducer"; + + enum TransactionCode : uint + { + RequestBuffer = 1, + SetBufferCount, + DequeueBuffer, + DetachBuffer, + DetachNextBuffer, + AttachBuffer, + QueueBuffer, + CancelBuffer, + Query, + Connect, + Disconnect, + SetSidebandStream, + AllocateBuffers, + SetPreallocatedBuffer, + Reserved15, + GetBufferInfo, + GetBufferHistory + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x54)] + public struct QueueBufferInput : IFlattenable + { + public long Timestamp; + public int IsAutoTimestamp; + public Rect Crop; + public NativeWindowScalingMode ScalingMode; + public NativeWindowTransform Transform; + public uint StickyTransform; + public int Async; + public int SwapInterval; + public AndroidFence Fence; + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public uint GetFdCount() + { + return 0; + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf<QueueBufferInput>(); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType<QueueBufferInput>(); + } + } + + public struct QueueBufferOutput + { + public uint Width; + public uint Height; + public NativeWindowTransform TransformHint; + public uint NumPendingBuffers; + public ulong FrameNumber; + + public void WriteToParcel(Parcel parcel) + { + parcel.WriteUInt32(Width); + parcel.WriteUInt32(Height); + parcel.WriteUnmanagedType(ref TransformHint); + parcel.WriteUInt32(NumPendingBuffers); + + if (TransformHint.HasFlag(NativeWindowTransform.ReturnFrameNumber)) + { + parcel.WriteUInt64(FrameNumber); + } + } + } + + public ResultCode AdjustRefcount(int addVal, int type) + { + // TODO? + return ResultCode.Success; + } + + public void GetNativeHandle(uint typeId, out KReadableEvent readableEvent) + { + if (typeId == 0xF) + { + readableEvent = GetWaitBufferFreeEvent(); + } + else + { + throw new NotImplementedException($"Unimplemented native event type {typeId}!"); + } + } + + public void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel) + { + Status status = Status.Success; + int slot; + AndroidFence fence; + QueueBufferInput queueInput; + QueueBufferOutput queueOutput; + NativeWindowApi api; + + AndroidStrongPointer<GraphicBuffer> graphicBuffer; + AndroidStrongPointer<AndroidFence> strongFence; + + switch ((TransactionCode)code) + { + case TransactionCode.RequestBuffer: + slot = inputParcel.ReadInt32(); + + status = RequestBuffer(slot, out graphicBuffer); + + outputParcel.WriteStrongPointer(ref graphicBuffer); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.SetBufferCount: + int bufferCount = inputParcel.ReadInt32(); + + status = SetBufferCount(bufferCount); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DequeueBuffer: + bool async = inputParcel.ReadBoolean(); + uint width = inputParcel.ReadUInt32(); + uint height = inputParcel.ReadUInt32(); + PixelFormat format = inputParcel.ReadUnmanagedType<PixelFormat>(); + uint usage = inputParcel.ReadUInt32(); + + status = DequeueBuffer(out int dequeueSlot, out fence, async, width, height, format, usage); + strongFence = new AndroidStrongPointer<AndroidFence>(fence); + + outputParcel.WriteInt32(dequeueSlot); + outputParcel.WriteStrongPointer(ref strongFence); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DetachBuffer: + slot = inputParcel.ReadInt32(); + + status = DetachBuffer(slot); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DetachNextBuffer: + status = DetachNextBuffer(out graphicBuffer, out fence); + strongFence = new AndroidStrongPointer<AndroidFence>(fence); + + outputParcel.WriteStrongPointer(ref graphicBuffer); + outputParcel.WriteStrongPointer(ref strongFence); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.AttachBuffer: + graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>(); + + status = AttachBuffer(out slot, graphicBuffer); + + outputParcel.WriteInt32(slot); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.QueueBuffer: + slot = inputParcel.ReadInt32(); + queueInput = inputParcel.ReadFlattenable<QueueBufferInput>(); + + status = QueueBuffer(slot, ref queueInput, out queueOutput); + + queueOutput.WriteToParcel(outputParcel); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.CancelBuffer: + slot = inputParcel.ReadInt32(); + fence = inputParcel.ReadFlattenable<AndroidFence>(); + + CancelBuffer(slot, ref fence); + + outputParcel.WriteStatus(Status.Success); + + break; + case TransactionCode.Query: + NativeWindowAttribute what = inputParcel.ReadUnmanagedType<NativeWindowAttribute>(); + + status = Query(what, out int outValue); + + outputParcel.WriteInt32(outValue); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.Connect: + bool hasListener = inputParcel.ReadBoolean(); + + IProducerListener listener = null; + + if (hasListener) + { + throw new NotImplementedException("Connect with a strong binder listener isn't implemented"); + } + + api = inputParcel.ReadUnmanagedType<NativeWindowApi>(); + + bool producerControlledByApp = inputParcel.ReadBoolean(); + + status = Connect(listener, api, producerControlledByApp, out queueOutput); + + queueOutput.WriteToParcel(outputParcel); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.Disconnect: + api = inputParcel.ReadUnmanagedType<NativeWindowApi>(); + + status = Disconnect(api); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.SetPreallocatedBuffer: + slot = inputParcel.ReadInt32(); + + graphicBuffer = inputParcel.ReadStrongPointer<GraphicBuffer>(); + + status = SetPreallocatedBuffer(slot, graphicBuffer); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.GetBufferHistory: + int bufferHistoryCount = inputParcel.ReadInt32(); + + status = GetBufferHistory(bufferHistoryCount, out Span<BufferInfo> bufferInfos); + + outputParcel.WriteStatus(status); + + outputParcel.WriteInt32(bufferInfos.Length); + + outputParcel.WriteUnmanagedSpan<BufferInfo>(bufferInfos); + + break; + default: + throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented"); + } + + if (status != Status.Success) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Error returned by transaction {(TransactionCode)code}: {status}"); + } + } + + protected abstract KReadableEvent GetWaitBufferFreeEvent(); + + public abstract Status RequestBuffer(int slot, out AndroidStrongPointer<GraphicBuffer> graphicBuffer); + + public abstract Status SetBufferCount(int bufferCount); + + public abstract Status DequeueBuffer(out int slot, out AndroidFence fence, bool async, uint width, uint height, PixelFormat format, uint usage); + + public abstract Status DetachBuffer(int slot); + + public abstract Status DetachNextBuffer(out AndroidStrongPointer<GraphicBuffer> graphicBuffer, out AndroidFence fence); + + public abstract Status AttachBuffer(out int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer); + + public abstract Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output); + + public abstract void CancelBuffer(int slot, ref AndroidFence fence); + + public abstract Status Query(NativeWindowAttribute what, out int outValue); + + public abstract Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output); + + public abstract Status Disconnect(NativeWindowApi api); + + public abstract Status SetPreallocatedBuffer(int slot, AndroidStrongPointer<GraphicBuffer> graphicBuffer); + + public abstract Status GetBufferHistory(int bufferHistoryCount, out Span<BufferInfo> bufferInfos); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs new file mode 100644 index 00000000..42fc2761 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IHOSBinderDriver : IpcService + { + public IHOSBinderDriver() { } + + [CommandCmif(0)] + // TransactParcel(s32, u32, u32, buffer<unknown, 5, 0>) -> buffer<unknown, 6, 0> + public ResultCode TransactParcel(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + ulong dataPos = context.Request.SendBuff[0].Position; + ulong dataSize = context.Request.SendBuff[0].Size; + + ulong replyPos = context.Request.ReceiveBuff[0].Position; + ulong replySize = context.Request.ReceiveBuff[0].Size; + + ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); + + Span<byte> outputParcel = new Span<byte>(new byte[replySize]); + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.Write(replyPos, outputParcel); + } + + return result; + } + + [CommandCmif(1)] + // AdjustRefcount(s32, s32, s32) + public ResultCode AdjustRefcount(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + int addVal = context.RequestData.ReadInt32(); + int type = context.RequestData.ReadInt32(); + + return AdjustRefcount(binderId, addVal, type); + } + + [CommandCmif(2)] + // GetNativeHandle(s32, s32) -> handle<copy> + public ResultCode GetNativeHandle(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint typeId = context.RequestData.ReadUInt32(); + + GetNativeHandle(binderId, typeId, out KReadableEvent readableEvent); + + if (context.Process.HandleTable.GenerateHandle(readableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] // 3.0.0+ + // TransactParcelAuto(s32, u32, u32, buffer<unknown, 21, 0>) -> buffer<unknown, 22, 0> + public ResultCode TransactParcelAuto(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + (ulong dataPos, ulong dataSize) = context.Request.GetBufferType0x21(); + (ulong replyPos, ulong replySize) = context.Request.GetBufferType0x22(); + + ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); + + using (IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.Shared.RentCleared(replySize)) + { + Span<byte> outputParcel = outputParcelOwner.Memory.Span; + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.Write(replyPos, outputParcel); + } + + return result; + } + } + + protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type); + + protected abstract void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent); + + protected abstract ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs new file mode 100644 index 00000000..43d2101e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IProducerListener + { + void OnBufferReleased(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs new file mode 100644 index 00000000..5f014e13 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum LayerState + { + NotInitialized, + ManagedClosed, + ManagedOpened, + Stray + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs new file mode 100644 index 00000000..1ae2732f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowApi : int + { + NoApi = 0, + NVN = 1, + CPU = 2, + Media = 3, + Camera = 4 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs new file mode 100644 index 00000000..c40b4fa1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowAttribute : uint + { + Width = 0, + Height = 1, + Format = 2, + MinUnqueuedBuffers = 3, + ConsumerRunningBehind = 9, + ConsumerUsageBits = 10, + MaxBufferCountAsync = 12 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs new file mode 100644 index 00000000..4194c915 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowScalingMode : uint + { + Freeze = 0, + ScaleToWindow = 1, + ScaleCrop = 2, + Unknown = 3, + NoScaleCrop = 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs new file mode 100644 index 00000000..66482b12 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [Flags] + enum NativeWindowTransform : uint + { + None = 0, + FlipX = 1, + FlipY = 2, + Rotate90 = 4, + Rotate180 = FlipX | FlipY, + Rotate270 = Rotate90 | Rotate180, + InverseDisplay = 8, + NoVSyncCapability = 0x10, + ReturnFrameNumber = 0x20 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs new file mode 100644 index 00000000..19b22157 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs @@ -0,0 +1,221 @@ +using Ryujinx.Common; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class Parcel + { + private readonly byte[] _rawData; + + private Span<byte> Raw => new Span<byte>(_rawData); + + private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0]; + + private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize); + + private Span<byte> Objects => Raw.Slice((int)Header.ObjectOffset, (int)Header.ObjectsSize); + + private int _payloadPosition; + private int _objectPosition; + + public Parcel(byte[] rawData) + { + _rawData = rawData; + + _payloadPosition = 0; + _objectPosition = 0; + } + + public Parcel(uint payloadSize, uint objectsSize) + { + uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>(); + + _rawData = new byte[BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)]; + + Header.PayloadSize = payloadSize; + Header.ObjectsSize = objectsSize; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.ObjectsSize; + } + + public string ReadInterfaceToken() + { + // Ignore the policy flags + int strictPolicy = ReadInt32(); + + return ReadString16(); + } + + public string ReadString16() + { + int size = ReadInt32(); + + if (size < 0) + { + return ""; + } + + ReadOnlySpan<byte> data = ReadInPlace((size + 1) * 2); + + // Return the unicode string without the last character (null terminator) + return Encoding.Unicode.GetString(data.Slice(0, size * 2)); + } + + public int ReadInt32() => ReadUnmanagedType<int>(); + public uint ReadUInt32() => ReadUnmanagedType<uint>(); + public bool ReadBoolean() => ReadUnmanagedType<uint>() != 0; + public long ReadInt64() => ReadUnmanagedType<long>(); + public ulong ReadUInt64() => ReadUnmanagedType<ulong>(); + + public T ReadFlattenable<T>() where T : unmanaged, IFlattenable + { + long flattenableSize = ReadInt64(); + + T result = new T(); + + Debug.Assert(flattenableSize == result.GetFlattenedSize()); + + result.Unflatten(this); + + return result; + } + + public T ReadUnmanagedType<T>() where T: unmanaged + { + ReadOnlySpan<byte> data = ReadInPlace(Unsafe.SizeOf<T>()); + + return MemoryMarshal.Cast<byte, T>(data)[0]; + } + + public ReadOnlySpan<byte> ReadInPlace(int size) + { + ReadOnlySpan<byte> result = Payload.Slice(_payloadPosition, size); + + _payloadPosition += BitUtils.AlignUp(size, 4); + + return result; + } + + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + private struct FlatBinderObject + { + public int Type; + public int Flags; + public long BinderId; + public long Cookie; + + private byte _serviceNameStart; + + public Span<byte> ServiceName => MemoryMarshal.CreateSpan(ref _serviceNameStart, 0x8); + } + + public void WriteObject<T>(T obj, string serviceName) where T: IBinder + { + FlatBinderObject flatBinderObject = new FlatBinderObject + { + Type = 2, + Flags = 0, + BinderId = HOSBinderDriverServer.GetBinderId(obj), + }; + + Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName); + + WriteUnmanagedType(ref flatBinderObject); + + // TODO: figure out what this value is + + WriteInplaceObject(new byte[4] { 0, 0, 0, 0 }); + } + + public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable + { + bool hasObject = ReadBoolean(); + + if (hasObject) + { + T obj = ReadFlattenable<T>(); + + return new AndroidStrongPointer<T>(obj); + } + else + { + return new AndroidStrongPointer<T>(); + } + } + + public void WriteStrongPointer<T>(ref AndroidStrongPointer<T> value) where T: unmanaged, IFlattenable + { + WriteBoolean(!value.IsNull); + + if (!value.IsNull) + { + WriteFlattenable<T>(ref value.Object); + } + } + + public void WriteFlattenable<T>(ref T value) where T : unmanaged, IFlattenable + { + WriteInt64(value.GetFlattenedSize()); + + value.Flatten(this); + } + + public void WriteStatus(Status status) => WriteUnmanagedType(ref status); + public void WriteBoolean(bool value) => WriteUnmanagedType(ref value); + public void WriteInt32(int value) => WriteUnmanagedType(ref value); + public void WriteUInt32(uint value) => WriteUnmanagedType(ref value); + public void WriteInt64(long value) => WriteUnmanagedType(ref value); + public void WriteUInt64(ulong value) => WriteUnmanagedType(ref value); + + public void WriteUnmanagedSpan<T>(ReadOnlySpan<T> value) where T : unmanaged + { + WriteInplace(MemoryMarshal.Cast<T, byte>(value)); + } + + public void WriteUnmanagedType<T>(ref T value) where T : unmanaged + { + WriteInplace(SpanHelpers.AsByteSpan(ref value)); + } + + public void WriteInplace(ReadOnlySpan<byte> data) + { + Span<byte> result = Payload.Slice(_payloadPosition, data.Length); + + data.CopyTo(result); + + _payloadPosition += BitUtils.AlignUp(data.Length, 4); + } + + public void WriteInplaceObject(ReadOnlySpan<byte> data) + { + Span<byte> result = Objects.Slice(_objectPosition, data.Length); + + data.CopyTo(result); + + _objectPosition += BitUtils.AlignUp(data.Length, 4); + } + + private void UpdateHeader() + { + uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>(); + + Header.PayloadSize = (uint)_payloadPosition; + Header.ObjectsSize = (uint)_objectPosition; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.PayloadSize; + } + + public ReadOnlySpan<byte> Finish() + { + UpdateHeader(); + + return Raw.Slice(0, (int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs new file mode 100644 index 00000000..27068af2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + struct ParcelHeader + { + public uint PayloadSize; + public uint PayloadOffset; + public uint ObjectsSize; + public uint ObjectOffset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs new file mode 100644 index 00000000..c0ddea10 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum PixelFormat : uint + { + Unknown, + Rgba8888, + Rgbx8888, + Rgb888, + Rgb565, + Bgra8888, + Rgba5551, + Rgba4444, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs new file mode 100644 index 00000000..5a151902 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum Status : int + { + Success = 0, + WouldBlock = -11, + NoMemory = -12, + Busy = -16, + NoInit = -19, + BadValue = -22, + InvalidOperation = -37, + + // Producer flags + BufferNeedsReallocation = 1, + ReleaseAllBuffers = 2, + + // Consumer errors + StaleBufferSlot = 1, + NoBufferAvailaible = 2, + PresentLater = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs new file mode 100644 index 00000000..c7cddf10 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -0,0 +1,548 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + using ResultCode = Ryujinx.HLE.HOS.Services.Vi.ResultCode; + + class SurfaceFlinger : IConsumerListener, IDisposable + { + private const int TargetFps = 60; + + private Switch _device; + + private Dictionary<long, Layer> _layers; + + private bool _isRunning; + + private Thread _composerThread; + + private Stopwatch _chrono; + + private ManualResetEvent _event = new ManualResetEvent(false); + private AutoResetEvent _nextFrameEvent = new AutoResetEvent(true); + private long _ticks; + private long _ticksPerFrame; + private long _spinTicks; + private long _1msTicks; + + private int _swapInterval; + private int _swapIntervalDelay; + + private readonly object Lock = new object(); + + public long RenderLayerId { get; private set; } + + private class Layer + { + public int ProducerBinderId; + public IGraphicBufferProducer Producer; + public BufferItemConsumer Consumer; + public BufferQueueCore Core; + public ulong Owner; + public LayerState State; + } + + private class TextureCallbackInformation + { + public Layer Layer; + public BufferItem Item; + } + + public SurfaceFlinger(Switch device) + { + _device = device; + _layers = new Dictionary<long, Layer>(); + RenderLayerId = 0; + + _composerThread = new Thread(HandleComposition) + { + Name = "SurfaceFlinger.Composer" + }; + + _chrono = new Stopwatch(); + _chrono.Start(); + + _ticks = 0; + _spinTicks = Stopwatch.Frequency / 500; + _1msTicks = Stopwatch.Frequency / 1000; + + UpdateSwapInterval(1); + + _composerThread.Start(); + } + + private void UpdateSwapInterval(int swapInterval) + { + _swapInterval = swapInterval; + + // If the swap interval is 0, Game VSync is disabled. + if (_swapInterval == 0) + { + _nextFrameEvent.Set(); + _ticksPerFrame = 1; + } + else + { + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + } + } + + public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed) + { + layerId = 1; + + lock (Lock) + { + foreach (KeyValuePair<long, Layer> pair in _layers) + { + if (pair.Key >= layerId) + { + layerId = pair.Key + 1; + } + } + } + + CreateLayerFromId(pid, layerId, initialState); + + return GetProducerByLayerId(layerId); + } + + private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState) + { + lock (Lock) + { + Logger.Info?.Print(LogClass.SurfaceFlinger, $"Creating layer {layerId}"); + + BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer); + + core.BufferQueued += () => + { + _nextFrameEvent.Set(); + }; + + _layers.Add(layerId, new Layer + { + ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer), + Producer = producer, + Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this), + Core = core, + Owner = pid, + State = initialState + }); + } + } + + public ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null || layer.State != LayerState.ManagedClosed) + { + producer = null; + + return ResultCode.InvalidArguments; + } + + layer.State = LayerState.ManagedOpened; + producer = layer.Producer; + + return ResultCode.Success; + } + + public ResultCode CloseLayer(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}"); + + return ResultCode.InvalidValue; + } + + CloseLayer(layerId, layer); + + return ResultCode.Success; + } + } + + public ResultCode DestroyManagedLayer(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)"); + + return ResultCode.InvalidValue; + } + + if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)"); + + return ResultCode.PermissionDenied; + } + + HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId); + + if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened) + { + CloseLayer(layerId, layer); + } + + return ResultCode.Success; + } + } + + public ResultCode DestroyStrayLayer(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)"); + + return ResultCode.InvalidValue; + } + + if (layer.State != LayerState.Stray) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)"); + + return ResultCode.PermissionDenied; + } + + HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId); + + if (_layers.Remove(layerId)) + { + CloseLayer(layerId, layer); + } + + return ResultCode.Success; + } + } + + private void CloseLayer(long layerId, Layer layer) + { + // If the layer was removed and the current in use, we need to change the current layer in use. + if (RenderLayerId == layerId) + { + // If no layer is availaible, reset to default value. + if (_layers.Count == 0) + { + SetRenderLayer(0); + } + else + { + SetRenderLayer(_layers.Last().Key); + } + } + + if (layer.State == LayerState.ManagedOpened) + { + layer.State = LayerState.ManagedClosed; + } + } + + public void SetRenderLayer(long layerId) + { + lock (Lock) + { + RenderLayerId = layerId; + } + } + + private Layer GetLayerByIdLocked(long layerId) + { + foreach (KeyValuePair<long, Layer> pair in _layers) + { + if (pair.Key == layerId) + { + return pair.Value; + } + } + + return null; + } + + public IGraphicBufferProducer GetProducerByLayerId(long layerId) + { + lock (Lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer != null) + { + return layer.Producer; + } + } + + return null; + } + + private void HandleComposition() + { + _isRunning = true; + + long lastTicks = _chrono.ElapsedTicks; + + while (_isRunning) + { + long ticks = _chrono.ElapsedTicks; + + if (_swapInterval == 0) + { + Compose(); + + _device.System?.SignalVsync(); + + _nextFrameEvent.WaitOne(17); + lastTicks = ticks; + } + else + { + _ticks += ticks - lastTicks; + lastTicks = ticks; + + if (_ticks >= _ticksPerFrame) + { + if (_swapIntervalDelay-- == 0) + { + Compose(); + + // When a frame is presented, delay the next one by its swap interval value. + _swapIntervalDelay = Math.Max(0, _swapInterval - 1); + } + + _device.System?.SignalVsync(); + + // Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer. + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3); + } + + // Sleep if possible. If the time til the next frame is too low, spin wait instead. + long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks); + if (diff > 0) + { + if (diff < _spinTicks) + { + do + { + // SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks. + // The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time. + Thread.SpinWait(5); + + ticks = _chrono.ElapsedTicks; + _ticks += ticks - lastTicks; + lastTicks = ticks; + } while (_ticks < _ticksPerFrame); + } + else + { + _event.WaitOne((int)(diff / _1msTicks)); + } + } + } + } + } + + public void Compose() + { + lock (Lock) + { + // TODO: support multilayers (& multidisplay ?) + if (RenderLayerId == 0) + { + return; + } + + Layer layer = GetLayerByIdLocked(RenderLayerId); + + Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0); + + if (acquireStatus == Status.Success) + { + // If device vsync is disabled, reflect the change. + if (!_device.EnableDeviceVsync) + { + if (_swapInterval != 0) + { + UpdateSwapInterval(0); + } + } + else if (item.SwapInterval != _swapInterval) + { + UpdateSwapInterval(item.SwapInterval); + } + + PostFrameBuffer(layer, item); + } + else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation) + { + throw new InvalidOperationException(); + } + } + } + + private void PostFrameBuffer(Layer layer, BufferItem item) + { + int frameBufferWidth = item.GraphicBuffer.Object.Width; + int frameBufferHeight = item.GraphicBuffer.Object.Height; + + int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + + if (nvMapHandle == 0) + { + nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId; + } + + ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle); + + ulong frameBufferAddress = map.Address + bufferOffset; + + Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat); + + int bytesPerPixel = + format == Format.B5G6R5Unorm || + format == Format.R4G4B4A4Unorm ? 2 : 4; + + int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2; + + // Note: Rotation is being ignored. + Rect cropRect = item.Crop; + + bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX); + bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY); + + AspectRatio aspectRatio = _device.Configuration.AspectRatio; + bool isStretched = aspectRatio == AspectRatio.Stretched; + + ImageCrop crop = new ImageCrop( + cropRect.Left, + cropRect.Right, + cropRect.Top, + cropRect.Bottom, + flipX, + flipY, + isStretched, + aspectRatio.ToFloatX(), + aspectRatio.ToFloatY()); + + TextureCallbackInformation textureCallbackInformation = new TextureCallbackInformation + { + Layer = layer, + Item = item + }; + + if (_device.Gpu.Window.EnqueueFrameThreadSafe( + layer.Owner, + frameBufferAddress, + frameBufferWidth, + frameBufferHeight, + 0, + false, + gobBlocksInY, + format, + bytesPerPixel, + crop, + AcquireBuffer, + ReleaseBuffer, + textureCallbackInformation)) + { + if (item.Fence.FenceCount == 0) + { + _device.Gpu.Window.SignalFrameReady(); + _device.Gpu.GPFifo.Interrupt(); + } + else + { + item.Fence.RegisterCallback(_device.Gpu, (x) => + { + _device.Gpu.Window.SignalFrameReady(); + _device.Gpu.GPFifo.Interrupt(); + }); + } + } + else + { + ReleaseBuffer(textureCallbackInformation); + } + } + + private void ReleaseBuffer(object obj) + { + ReleaseBuffer((TextureCallbackInformation)obj); + } + + private void ReleaseBuffer(TextureCallbackInformation information) + { + AndroidFence fence = AndroidFence.NoFence; + + information.Layer.Consumer.ReleaseBuffer(information.Item, ref fence); + } + + private void AcquireBuffer(GpuContext ignored, object obj) + { + AcquireBuffer((TextureCallbackInformation)obj); + } + + private void AcquireBuffer(TextureCallbackInformation information) + { + information.Item.Fence.WaitForever(_device.Gpu); + } + + public static Format ConvertColorFormat(ColorFormat colorFormat) + { + return colorFormat switch + { + ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.R5G6B5 => Format.B5G6R5Unorm, + ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm, + ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm, + _ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"), + }; + } + + public void Dispose() + { + _isRunning = false; + + foreach (Layer layer in _layers.Values) + { + layer.Core.PrepareForExit(); + } + } + + public void OnFrameAvailable(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnFrameReplaced(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnBuffersReleased() {} + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs new file mode 100644 index 00000000..5b72e257 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs @@ -0,0 +1,104 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)] + struct AndroidFence : IFlattenable + { + public int FenceCount; + + private byte _fenceStorageStart; + + private Span<byte> _storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf<NvFence>() * 4); + + public Span<NvFence> NvFences => MemoryMarshal.Cast<byte, NvFence>(_storage); + + public static AndroidFence NoFence + { + get + { + AndroidFence fence = new AndroidFence + { + FenceCount = 0 + }; + + fence.NvFences[0].Id = NvFence.InvalidSyncPointId; + + return fence; + } + } + + public void AddFence(NvFence fence) + { + NvFences[FenceCount++] = fence; + } + + public void WaitForever(GpuContext gpuContext) + { + bool hasTimeout = Wait(gpuContext, TimeSpan.FromMilliseconds(3000)); + + if (hasTimeout) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "Android fence didn't signal in 3000 ms"); + Wait(gpuContext, Timeout.InfiniteTimeSpan); + } + + } + + public bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + for (int i = 0; i < FenceCount; i++) + { + bool hasTimeout = NvFences[i].Wait(gpuContext, timeout); + + if (hasTimeout) + { + return true; + } + } + + return false; + } + + public void RegisterCallback(GpuContext gpuContext, Action<SyncpointWaiterHandle> callback) + { + ref NvFence fence = ref NvFences[FenceCount - 1]; + + if (fence.IsValid()) + { + gpuContext.Synchronization.RegisterCallbackOnSyncpoint(fence.Id, fence.Value, callback); + } + else + { + callback(null); + } + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf<AndroidFence>(); + } + + public uint GetFdCount() + { + return 0; + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType<AndroidFence>(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs new file mode 100644 index 00000000..c356671b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types +{ + class AndroidStrongPointer<T> where T: unmanaged, IFlattenable + { + public T Object; + + private bool _hasObject; + + public bool IsNull => !_hasObject; + + public AndroidStrongPointer() + { + _hasObject = false; + } + + public AndroidStrongPointer(T obj) + { + Set(obj); + } + + public void Set(AndroidStrongPointer<T> other) + { + Object = other.Object; + _hasObject = other._hasObject; + } + + public void Set(T obj) + { + Object = obj; + _hasObject = true; + } + + public void Reset() + { + _hasObject = false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs new file mode 100644 index 00000000..12c41b0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1C, Pack = 1)] + struct BufferInfo + { + public ulong FrameNumber; + public TimeSpanType QueueTime; + public TimeSpanType PresentationTime; + public BufferState State; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs new file mode 100644 index 00000000..19fc7900 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs @@ -0,0 +1,62 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferItem : ICloneable + { + public AndroidStrongPointer<GraphicBuffer> GraphicBuffer; + public AndroidFence Fence; + public Rect Crop; + public NativeWindowTransform Transform; + public NativeWindowScalingMode ScalingMode; + public long Timestamp; + public bool IsAutoTimestamp; + public int SwapInterval; + public ulong FrameNumber; + public int Slot; + public bool IsDroppable; + public bool AcquireCalled; + public bool TransformToDisplayInverse; + + public BufferItem() + { + GraphicBuffer = new AndroidStrongPointer<GraphicBuffer>(); + Transform = NativeWindowTransform.None; + ScalingMode = NativeWindowScalingMode.Freeze; + Timestamp = 0; + IsAutoTimestamp = false; + FrameNumber = 0; + Slot = BufferSlotArray.InvalidBufferSlot; + IsDroppable = false; + AcquireCalled = false; + TransformToDisplayInverse = false; + SwapInterval = 1; + Fence = AndroidFence.NoFence; + + Crop = new Rect(); + Crop.MakeInvalid(); + } + + public object Clone() + { + BufferItem item = new BufferItem(); + + item.Transform = Transform; + item.ScalingMode = ScalingMode; + item.IsAutoTimestamp = IsAutoTimestamp; + item.FrameNumber = FrameNumber; + item.Slot = Slot; + item.IsDroppable = IsDroppable; + item.AcquireCalled = AcquireCalled; + item.TransformToDisplayInverse = TransformToDisplayInverse; + item.SwapInterval = SwapInterval; + item.Fence = Fence; + item.Crop = Crop; + + item.GraphicBuffer.Set(GraphicBuffer); + + return item; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs new file mode 100644 index 00000000..1787f5a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + internal enum BufferState + { + Free = 0, + Dequeued = 1, + Queued = 2, + Acquired = 3 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs new file mode 100644 index 00000000..b47d35b4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorBytePerPixel + { + Bpp1 = 1, + Bpp2 = 2, + Bpp4 = 4, + Bpp8 = 8, + Bpp16 = 16, + Bpp24 = 24, + Bpp32 = 32, + Bpp48 = 48, + Bpp64 = 64, + Bpp96 = 96, + Bpp128 = 128 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs new file mode 100644 index 00000000..e9669f12 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorComponent : uint + { + X1 = (0x01 << ColorShift.Component) | ColorBytePerPixel.Bpp1, + X2 = (0x02 << ColorShift.Component) | ColorBytePerPixel.Bpp2, + X4 = (0x03 << ColorShift.Component) | ColorBytePerPixel.Bpp4, + X8 = (0x04 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + Y4X4 = (0x05 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X3Y3Z2 = (0x06 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X8Y8 = (0x07 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8X8Z8 = (0x08 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8Z8X8 = (0x09 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X16 = (0x0A << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y2X14 = (0x0B << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y4X12 = (0x0C << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y6X10 = (0x0D << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8 = (0x0E << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X10 = (0x0F << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X12 = (0x10 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Z5Y5X6 = (0x11 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y6Z5 = (0x12 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X6Y5Z5 = (0x13 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X1Y5Z5W5 = (0x14 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X4Y4Z4W4 = (0x15 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y1Z5W5 = (0x16 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z1W5 = (0x17 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z5W1 = (0x18 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8Z8 = (0x19 << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X24 = (0x1A << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X32 = (0x1C << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X16Y16 = (0x1D << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X11Y11Z10 = (0x1E << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X2Y10Z10W10 = (0x20 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X8Y8Z8W8 = (0x21 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y10X10 = (0x22 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X10Y10Z10W2 = (0x23 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y12X12 = (0x24 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X20Y20Z20 = (0x26 << ColorShift.Component) | ColorBytePerPixel.Bpp64, + X16Y16Z16W16 = (0x27 << ColorShift.Component) | ColorBytePerPixel.Bpp64, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs new file mode 100644 index 00000000..cfa3b018 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorDataType + { + Integer = 0x0 << ColorShift.DataType, + Float = 0x1 << ColorShift.DataType, + Stencil = 0x2 << ColorShift.DataType + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs new file mode 100644 index 00000000..227d648a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs @@ -0,0 +1,235 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorFormat : ulong + { + NonColor8 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + NonColor16 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + NonColor24 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X24 | ColorDataType.Integer, + NonColor32 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X32 | ColorDataType.Integer, + X4C4 = ColorSpace.NonColor | ColorSwizzle.Y000 | ColorComponent.Y4X4 | ColorDataType.Integer, + A4L4 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y4X4 | ColorDataType.Integer, + A8L8 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y8X8 | ColorDataType.Integer, + Float_A16L16 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.X16Y16 | ColorDataType.Float, + A1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4B4G4R4 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5B5G5R1 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + A2B10G10R10 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4R4G4B4 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5R1G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X5Y1Z5W5 | ColorDataType.Integer, + A2R10G10B10 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A1 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X1 | ColorDataType.Integer, + A2 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X2 | ColorDataType.Integer, + A4 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X4 | ColorDataType.Integer, + A8 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X8 | ColorDataType.Integer, + A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Integer, + A32 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X32 | ColorDataType.Integer, + Float_A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Float, + L4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y4X4 | ColorDataType.Integer, + L8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y8X8 | ColorDataType.Integer, + B4G4R4A4 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + B5G5R1A5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z1W5 | ColorDataType.Integer, + B5G5R5A1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8G8R8A8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + B10G10R10A2 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R1G5B5A5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + R4G4B4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + R5G5B5A1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8G8B8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + R10G10B10A2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + L1 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X1 | ColorDataType.Integer, + L2 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X2 | ColorDataType.Integer, + L4 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X4 | ColorDataType.Integer, + L8 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X8 | ColorDataType.Integer, + L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Integer, + L32 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X32 | ColorDataType.Integer, + Float_L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Float, + B5G6R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + B6G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X6Y5Z5 | ColorDataType.Integer, + B5G5R5X1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8_G8_R8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + B8G8R8X8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + Float_B10G11R11 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X11Y11Z10 | ColorDataType.Float, + X1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + R3G3B2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X3Y3Z2 | ColorDataType.Integer, + R5G5B6 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.Z5Y5X6 | ColorDataType.Integer, + R5G6B5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + R5G5B5X1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8_G8_B8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + R8G8B8X8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + RG8 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.Y8X8 | ColorDataType.Integer, + R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Integer, + Float_R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Float, + R8 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X8 | ColorDataType.Integer, + R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Integer, + Float_R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Float, + A2B10G10R10_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_sRGB = ColorSpace.SRGB | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709 = ColorSpace.RGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709 = ColorSpace.RGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709 = ColorSpace.RGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_scRGB_Linear = ColorSpace.LinearScRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2B10G10R10_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_2020 = ColorSpace.RGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2R10G10B10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_PQ = ColorSpace.RGB2020_PQ | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A4I4 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y4X4 | ColorDataType.Integer, + A8I8 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y8X8 | ColorDataType.Integer, + I4A4 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y4X4 | ColorDataType.Integer, + I8A8 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y8X8 | ColorDataType.Integer, + I1 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X1 | ColorDataType.Integer, + I2 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X2 | ColorDataType.Integer, + I4 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X4 | ColorDataType.Integer, + I8 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + A8Y8U8V8 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16Y16U16V16 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Y8U8V8A8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + V8_U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + YVYU = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + VYUY = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + UYVY = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + YUYV = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + Y8_U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + V8_U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V8_U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V10U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U10V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + Bayer8RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + XYZ = ColorSpace.XYZ | ColorSwizzle.XYZ1 | ColorComponent.X20Y20Z20 | ColorDataType.Float, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs new file mode 100644 index 00000000..3ad216a8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class ColorShift + { + public const int Swizzle = 16; + public const int DataType = 14; + public const int Space = 32; + public const int Component = 8; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs new file mode 100644 index 00000000..9003a00b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSpace : ulong + { + NonColor = 0x0L << ColorShift.Space, + LinearRGBA = 0x1L << ColorShift.Space, + SRGB = 0x2L << ColorShift.Space, + + RGB709 = 0x3L << ColorShift.Space, + LinearRGB709 = 0x4L << ColorShift.Space, + + LinearScRGB = 0x5L << ColorShift.Space, + + RGB2020 = 0x6L << ColorShift.Space, + LinearRGB2020 = 0x7L << ColorShift.Space, + RGB2020_PQ = 0x8L << ColorShift.Space, + + ColorIndex = 0x9L << ColorShift.Space, + + YCbCr601 = 0xAL << ColorShift.Space, + YCbCr601_RR = 0xBL << ColorShift.Space, + YCbCr601_ER = 0xCL << ColorShift.Space, + YCbCr709 = 0xDL << ColorShift.Space, + YCbCr709_ER = 0xEL << ColorShift.Space, + + BayerRGGB = 0x10L << ColorShift.Space, + BayerBGGR = 0x11L << ColorShift.Space, + BayerGRBG = 0x12L << ColorShift.Space, + BayerGBRG = 0x13L << ColorShift.Space, + + XYZ = 0x14L << ColorShift.Space, + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs new file mode 100644 index 00000000..4c1370c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSwizzle + { + XYZW = 0x688 << ColorShift.Swizzle, + ZYXW = 0x60a << ColorShift.Swizzle, + WZYX = 0x053 << ColorShift.Swizzle, + YZWX = 0x0d1 << ColorShift.Swizzle, + XYZ1 = 0xa88 << ColorShift.Swizzle, + YZW1 = 0xad1 << ColorShift.Swizzle, + XXX1 = 0xa00 << ColorShift.Swizzle, + XZY1 = 0xa50 << ColorShift.Swizzle, + ZYX1 = 0xa0a << ColorShift.Swizzle, + WZY1 = 0xa53 << ColorShift.Swizzle, + X000 = 0x920 << ColorShift.Swizzle, + Y000 = 0x921 << ColorShift.Swizzle, + XY01 = 0xb08 << ColorShift.Swizzle, + X001 = 0xb20 << ColorShift.Swizzle, + X00X = 0x121 << ColorShift.Swizzle, + X00Y = 0x320 << ColorShift.Swizzle, + _0YX0 = 0x80c << ColorShift.Swizzle, + _0ZY0 = 0x814 << ColorShift.Swizzle, + _0XZ0 = 0x884 << ColorShift.Swizzle, + _0X00 = 0x904 << ColorShift.Swizzle, + _00X0 = 0x824 << ColorShift.Swizzle, + _000X = 0x124 << ColorShift.Swizzle, + _0XY0 = 0x844 << ColorShift.Swizzle, + XXXY = 0x200 << ColorShift.Swizzle, + YYYX = 0x049 << ColorShift.Swizzle + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs new file mode 100644 index 00000000..d1143225 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs @@ -0,0 +1,74 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct GraphicBuffer : IFlattenable + { + public GraphicBufferHeader Header; + public NvGraphicBuffer Buffer; + + public int Width => Header.Width; + public int Height => Header.Height; + public PixelFormat Format => Header.Format; + public int Usage => Header.Usage; + + public Rect ToRect() + { + return new Rect(Width, Height); + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref Header); + parcel.WriteUnmanagedType(ref Buffer); + } + + public void Unflatten(Parcel parcel) + { + Header = parcel.ReadUnmanagedType<GraphicBufferHeader>(); + + int expectedSize = Unsafe.SizeOf<NvGraphicBuffer>() / 4; + + if (Header.IntsCount != expectedSize) + { + throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x{expectedSize:x}, found 0x{Header.IntsCount:x})"); + } + + Buffer = parcel.ReadUnmanagedType<NvGraphicBuffer>(); + } + + public void IncrementNvMapHandleRefCount(ulong pid) + { + NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.NvMapId); + + for (int i = 0; i < Buffer.Surfaces.Length; i++) + { + NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle); + } + } + + public void DecrementNvMapHandleRefCount(ulong pid) + { + NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.NvMapId); + + for (int i = 0; i < Buffer.Surfaces.Length; i++) + { + NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle); + } + } + + public uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf<GraphicBuffer>(); + } + + public uint GetFdCount() + { + return 0; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs new file mode 100644 index 00000000..77495922 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)] + struct GraphicBufferHeader + { + public int Magic; + public int Width; + public int Height; + public int Stride; + public PixelFormat Format; + public int Usage; + + public int Pid; + public int RefCount; + + public int FdsCount; + public int IntsCount; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs new file mode 100644 index 00000000..6bb47dcc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)] + struct NvGraphicBuffer + { + [FieldOffset(0x4)] + public int NvMapId; + + [FieldOffset(0xC)] + public int Magic; + + [FieldOffset(0x10)] + public int Pid; + + [FieldOffset(0x14)] + public int Type; + + [FieldOffset(0x18)] + public int Usage; + + [FieldOffset(0x1C)] + public int PixelFormat; + + [FieldOffset(0x20)] + public int ExternalPixelFormat; + + [FieldOffset(0x24)] + public int Stride; + + [FieldOffset(0x28)] + public int FrameBufferSize; + + [FieldOffset(0x2C)] + public int PlanesCount; + + [FieldOffset(0x34)] + public NvGraphicBufferSurfaceArray Surfaces; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs new file mode 100644 index 00000000..e084bc73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x58)] + struct NvGraphicBufferSurface + { + [FieldOffset(0)] + public uint Width; + + [FieldOffset(0x4)] + public uint Height; + + [FieldOffset(0x8)] + public ColorFormat ColorFormat; + + [FieldOffset(0x10)] + public int Layout; + + [FieldOffset(0x14)] + public int Pitch; + + [FieldOffset(0x18)] + public int NvMapHandle; + + [FieldOffset(0x1C)] + public int Offset; + + [FieldOffset(0x20)] + public int Kind; + + [FieldOffset(0x24)] + public int BlockHeightLog2; + + [FieldOffset(0x28)] + public int ScanFormat; + + [FieldOffset(0x30)] + public long Flags; + + [FieldOffset(0x38)] + public long Size; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs new file mode 100644 index 00000000..51ac98f8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs @@ -0,0 +1,41 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit)] + struct NvGraphicBufferSurfaceArray + { + [FieldOffset(0x0)] + private NvGraphicBufferSurface Surface0; + + [FieldOffset(0x58)] + private NvGraphicBufferSurface Surface1; + + [FieldOffset(0xb0)] + private NvGraphicBufferSurface Surface2; + + public NvGraphicBufferSurface this[int index] + { + get + { + if (index == 0) + { + return Surface0; + } + else if (index == 1) + { + return Surface1; + } + else if (index == 2) + { + return Surface2; + } + + throw new IndexOutOfRangeException(); + } + } + + public int Length => 3; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs new file mode 100644 index 00000000..a5dec969 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct Rect : IEquatable<Rect> + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Width => Right - Left; + public int Height => Bottom - Top; + + public Rect(int width, int height) + { + Left = 0; + Top = 0; + Right = width; + Bottom = height; + } + + public bool IsEmpty() + { + return Width <= 0 || Height <= 0; + } + + public bool Intersect(Rect other, out Rect result) + { + result = new Rect + { + Left = Math.Max(Left, other.Left), + Top = Math.Max(Top, other.Top), + Right = Math.Min(Right, other.Right), + Bottom = Math.Min(Bottom, other.Bottom) + }; + + return !result.IsEmpty(); + } + + public void MakeInvalid() + { + Right = -1; + Bottom = -1; + } + + public static bool operator ==(Rect x, Rect y) + { + return x.Equals(y); + } + + public static bool operator !=(Rect x, Rect y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Rect rect && Equals(rect); + } + + public bool Equals(Rect cmpObj) + { + return Left == cmpObj.Left && Top == cmpObj.Top && Right == cmpObj.Right && Bottom == cmpObj.Bottom; + } + + public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs new file mode 100644 index 00000000..14d3cb24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + protected override ResultCode Update() + { + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs new file mode 100644 index 00000000..003863e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockCore : SystemClockCore + { + public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs new file mode 100644 index 00000000..fb7ebdc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class LocalSystemClockContextWriter : SystemClockContextUpdateCallback + { + private TimeSharedMemory _sharedMemory; + + public LocalSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateLocalSystemClockContext(_context); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs new file mode 100644 index 00000000..36468ec1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class NetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + private TimeSharedMemory _sharedMemory; + + public NetworkSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateNetworkSystemClockContext(_context); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs new file mode 100644 index 00000000..20c334e8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardLocalSystemClockCore : SystemClockCore + { + public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {} + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs new file mode 100644 index 00000000..aec03485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs @@ -0,0 +1,36 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardNetworkSystemClockCore : SystemClockCore + { + private TimeSpanType _standardNetworkClockSufficientAccuracy; + + public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) + { + _standardNetworkClockSufficientAccuracy = new TimeSpanType(0); + } + + public bool IsStandardNetworkSystemClockAccuracySufficient(ITickSource tickSource) + { + SteadyClockCore steadyClockCore = GetSteadyClockCore(); + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(tickSource); + + bool isStandardNetworkClockSufficientAccuracy = false; + + ResultCode result = GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success) + { + isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds; + } + + return isStandardNetworkClockSufficientAccuracy; + } + + public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy) + { + _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs new file mode 100644 index 00000000..8392c4b5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs @@ -0,0 +1,72 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardSteadyClockCore : SteadyClockCore + { + private TimeSpanType _setupValue; + private TimeSpanType _testOffset; + private TimeSpanType _internalOffset; + private TimeSpanType _cachedRawTimePoint; + + public StandardSteadyClockCore() + { + _setupValue = TimeSpanType.Zero; + _testOffset = TimeSpanType.Zero; + _internalOffset = TimeSpanType.Zero; + _cachedRawTimePoint = TimeSpanType.Zero; + } + + public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = new SteadyClockTimePoint + { + TimePoint = GetCurrentRawTimePoint(tickSource).ToSeconds(), + ClockSourceId = GetClockSourceId() + }; + + return result; + } + + public override TimeSpanType GetTestOffset() + { + return _testOffset; + } + + public override void SetTestOffset(TimeSpanType testOffset) + { + _testOffset = testOffset; + } + + public override TimeSpanType GetInternalOffset() + { + return _internalOffset; + } + + public override void SetInternalOffset(TimeSpanType internalOffset) + { + _internalOffset = internalOffset; + } + + public override TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds); + + if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds) + { + rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds; + } + + _cachedRawTimePoint = rawTimePoint; + + return rawTimePoint; + } + + public void SetSetupValue(TimeSpanType setupValue) + { + _setupValue = setupValue; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs new file mode 100644 index 00000000..fa485437 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs @@ -0,0 +1,108 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardUserSystemClockCore : SystemClockCore + { + private StandardLocalSystemClockCore _localSystemClockCore; + private StandardNetworkSystemClockCore _networkSystemClockCore; + private bool _autoCorrectionEnabled; + private SteadyClockTimePoint _autoCorrectionTime; + private KEvent _autoCorrectionEvent; + + public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore()) + { + _localSystemClockCore = localSystemClockCore; + _networkSystemClockCore = networkSystemClockCore; + _autoCorrectionEnabled = false; + _autoCorrectionTime = SteadyClockTimePoint.GetRandom(); + _autoCorrectionEvent = null; + } + + protected override ResultCode Flush(SystemClockContext context) + { + // As UserSystemClock isn't a real system clock, this shouldn't happens. + throw new NotImplementedException(); + } + + public override ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context) + { + ResultCode result = ApplyAutomaticCorrection(tickSource, false); + + context = new SystemClockContext(); + + if (result == ResultCode.Success) + { + return _localSystemClockCore.GetClockContext(tickSource, out context); + } + + return result; + } + + public override ResultCode SetClockContext(SystemClockContext context) + { + return ResultCode.NotImplemented; + } + + private ResultCode ApplyAutomaticCorrection(ITickSource tickSource, bool autoCorrectionEnabled) + { + ResultCode result = ResultCode.Success; + + if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(tickSource)) + { + result = _networkSystemClockCore.GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success) + { + _localSystemClockCore.SetClockContext(context); + } + } + + return result; + } + + internal void CreateAutomaticCorrectionEvent(Horizon system) + { + _autoCorrectionEvent = new KEvent(system.KernelContext); + } + + public ResultCode SetAutomaticCorrectionEnabled(ITickSource tickSource, bool autoCorrectionEnabled) + { + ResultCode result = ApplyAutomaticCorrection(tickSource, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _autoCorrectionEnabled = autoCorrectionEnabled; + } + + return result; + } + + public bool IsAutomaticCorrectionEnabled() + { + return _autoCorrectionEnabled; + } + + public KReadableEvent GetAutomaticCorrectionReadableEvent() + { + return _autoCorrectionEvent.ReadableEvent; + } + + public void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steadyClockTimePoint) + { + _autoCorrectionTime = steadyClockTimePoint; + } + + public SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime() + { + return _autoCorrectionTime; + } + + public void SignalAutomaticCorrectionEvent() + { + _autoCorrectionEvent.WritableEvent.Signal(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs new file mode 100644 index 00000000..18da4ed3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -0,0 +1,98 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SteadyClockCore + { + private UInt128 _clockSourceId; + private bool _isRtcResetDetected; + private bool _isInitialized; + + public SteadyClockCore() + { + _clockSourceId = UInt128Utils.CreateRandom(); + _isRtcResetDetected = false; + _isInitialized = false; + } + + public UInt128 GetClockSourceId() + { + return _clockSourceId; + } + + public void SetClockSourceId(UInt128 clockSourceId) + { + _clockSourceId = clockSourceId; + } + + public void SetRtcReset() + { + _isRtcResetDetected = true; + } + + public virtual TimeSpanType GetTestOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetTestOffset(TimeSpanType testOffset) {} + + public ResultCode GetRtcValue(out ulong rtcValue) + { + rtcValue = 0; + + return ResultCode.NotImplemented; + } + + public bool IsRtcResetDetected() + { + return _isRtcResetDetected; + } + + public ResultCode GetSetupResultValue() + { + return ResultCode.Success; + } + + public virtual TimeSpanType GetInternalOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetInternalOffset(TimeSpanType internalOffset) {} + + public virtual SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + throw new NotImplementedException(); + } + + public virtual TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint timePoint = GetTimePoint(tickSource); + + return TimeSpanType.FromSeconds(timePoint.TimePoint); + } + + public SteadyClockTimePoint GetCurrentTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = GetTimePoint(tickSource); + + result.TimePoint += GetTestOffset().ToSeconds(); + result.TimePoint += GetInternalOffset().ToSeconds(); + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs new file mode 100644 index 00000000..6229f5ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs @@ -0,0 +1,71 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockContextUpdateCallback + { + private List<KWritableEvent> _operationEventList; + protected SystemClockContext _context; + private bool _hasContext; + + public SystemClockContextUpdateCallback() + { + _operationEventList = new List<KWritableEvent>(); + _context = new SystemClockContext(); + _hasContext = false; + } + + private bool NeedUpdate(SystemClockContext context) + { + if (_hasContext) + { + return _context.Offset != context.Offset || _context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId; + } + + return true; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + Monitor.Enter(_operationEventList); + _operationEventList.Add(writableEvent); + Monitor.Exit(_operationEventList); + } + + private void BroadcastOperationEvent() + { + Monitor.Enter(_operationEventList); + + foreach (KWritableEvent e in _operationEventList) + { + e.Signal(); + } + + Monitor.Exit(_operationEventList); + } + + protected abstract ResultCode Update(); + + public ResultCode Update(SystemClockContext context) + { + ResultCode result = ResultCode.Success; + + if (NeedUpdate(context)) + { + _context = context; + _hasContext = true; + + result = Update(); + + if (result == ResultCode.Success) + { + BroadcastOperationEvent(); + } + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs new file mode 100644 index 00000000..f4bbaa60 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs @@ -0,0 +1,144 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockCore + { + private SteadyClockCore _steadyClockCore; + private SystemClockContext _context; + private bool _isInitialized; + private SystemClockContextUpdateCallback _systemClockContextUpdateCallback; + + public SystemClockCore(SteadyClockCore steadyClockCore) + { + _steadyClockCore = steadyClockCore; + _context = new SystemClockContext(); + _isInitialized = false; + + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + _systemClockContextUpdateCallback = null; + } + + public virtual SteadyClockCore GetSteadyClockCore() + { + return _steadyClockCore; + } + + public ResultCode GetCurrentTime(ITickSource tickSource, out long posixTime) + { + posixTime = 0; + + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + ResultCode result = GetClockContext(tickSource, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + posixTime = clockContext.Offset + currentTimePoint.TimePoint; + + result = 0; + } + } + + return result; + } + + public ResultCode SetCurrentTime(ITickSource tickSource, long posixTime) + { + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + SystemClockContext clockContext = new SystemClockContext() + { + Offset = posixTime - currentTimePoint.TimePoint, + SteadyTimePoint = currentTimePoint + }; + + ResultCode result = SetClockContext(clockContext); + + if (result == ResultCode.Success) + { + result = Flush(clockContext); + } + + return result; + } + + public virtual ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context) + { + context = _context; + + return ResultCode.Success; + } + + public virtual ResultCode SetClockContext(SystemClockContext context) + { + _context = context; + + return ResultCode.Success; + } + + protected virtual ResultCode Flush(SystemClockContext context) + { + if (_systemClockContextUpdateCallback == null) + { + return ResultCode.Success; + } + + return _systemClockContextUpdateCallback.Update(context); + } + + public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback) + { + _systemClockContextUpdateCallback = systemClockContextUpdateCallback; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + if (_systemClockContextUpdateCallback != null) + { + _systemClockContextUpdateCallback.RegisterOperationEvent(writableEvent); + } + } + + public ResultCode SetSystemClockContext(SystemClockContext context) + { + ResultCode result = SetClockContext(context); + + if (result == ResultCode.Success) + { + result = Flush(context); + } + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + + public bool IsClockSetup(ITickSource tickSource) + { + ResultCode result = GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success) + { + SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId; + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs new file mode 100644 index 00000000..fe74da7e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs @@ -0,0 +1,24 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class TickBasedSteadyClockCore : SteadyClockCore + { + public TickBasedSteadyClockCore() {} + + public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = GetClockSourceId() + }; + + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + result.TimePoint = ticksTimeSpan.ToSeconds(); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs new file mode 100644 index 00000000..07c1b405 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs @@ -0,0 +1,50 @@ +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Size = 0xD0)] + struct ClockSnapshot + { + public SystemClockContext UserContext; + public SystemClockContext NetworkContext; + public long UserTime; + public long NetworkTime; + public CalendarTime UserCalendarTime; + public CalendarTime NetworkCalendarTime; + public CalendarAdditionalInfo UserCalendarAdditionalTime; + public CalendarAdditionalInfo NetworkCalendarAdditionalTime; + public SteadyClockTimePoint SteadyClockTimePoint; + + private LocationNameStorageHolder _locationName; + + public Span<byte> LocationName => MemoryMarshal.Cast<LocationNameStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size)); + + [MarshalAs(UnmanagedType.I1)] + public bool IsAutomaticCorrectionEnabled; + public byte Type; + public ushort Unknown; + + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] + private struct LocationNameStorageHolder + { + public const int Size = 0x24; + } + + public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context) + { + currentTime = 0; + + if (steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId) + { + currentTime = steadyClockTimePoint.TimePoint + context.Offset; + + return ResultCode.Success; + } + + return ResultCode.TimeMismatch; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs new file mode 100644 index 00000000..729e11b6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs @@ -0,0 +1,43 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SteadyClockTimePoint + { + public long TimePoint; + public UInt128 ClockSourceId; + + public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan) + { + outSpan = 0; + + if (ClockSourceId == other.ClockSourceId) + { + try + { + outSpan = checked(other.TimePoint - TimePoint); + + return ResultCode.Success; + } + catch (OverflowException) + { + return ResultCode.Overflow; + } + } + + return ResultCode.Overflow; + } + + public static SteadyClockTimePoint GetRandom() + { + return new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = UInt128Utils.CreateRandom() + }; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs new file mode 100644 index 00000000..6b589c28 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SystemClockContext + { + public long Offset; + public SteadyClockTimePoint SteadyTimePoint; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs new file mode 100644 index 00000000..0070193f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + struct TimeSpanType + { + private const long NanoSecondsPerSecond = 1000000000; + + public static readonly TimeSpanType Zero = new TimeSpanType(0); + + public long NanoSeconds; + + public TimeSpanType(long nanoSeconds) + { + NanoSeconds = nanoSeconds; + } + + public long ToSeconds() + { + return NanoSeconds / NanoSecondsPerSecond; + } + + public TimeSpanType AddSeconds(long seconds) + { + return new TimeSpanType(NanoSeconds + (seconds * NanoSecondsPerSecond)); + } + + public bool IsDaylightSavingTime() + { + return DateTime.UnixEpoch.AddSeconds(ToSeconds()).ToLocalTime().IsDaylightSavingTime(); + } + + public static TimeSpanType FromSeconds(long seconds) + { + return new TimeSpanType(seconds * NanoSecondsPerSecond); + } + + public static TimeSpanType FromTimeSpan(TimeSpan timeSpan) + { + return new TimeSpanType((long)(timeSpan.TotalMilliseconds * 1000000)); + } + + public static TimeSpanType FromTicks(ulong ticks, ulong frequency) + { + return FromSeconds((long)ticks / (long)frequency); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs b/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs new file mode 100644 index 00000000..092fa8ce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:al")] // 9.0.0+ + class IAlarmService : IpcService + { + public IAlarmService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs b/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs new file mode 100644 index 00000000..8ec55c15 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:p")] // 9.0.0+ + class IPowerStateRequestHandler : IpcService + { + public IPowerStateRequestHandler(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs new file mode 100644 index 00000000..31548b80 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs @@ -0,0 +1,184 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Pcv.Bpc; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:a", TimePermissions.Admin)] + [Service("time:r", TimePermissions.Repair)] + [Service("time:u", TimePermissions.User)] + class IStaticServiceForGlue : IpcService + { + private IStaticServiceForPsc _inner; + private TimePermissions _permissions; + + public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions) : base(context.Device.System.TimeServer) + { + _permissions = permissions; + _inner = new IStaticServiceForPsc(context, permissions); + _inner.TrySetServer(Server); + _inner.SetParent(this); + } + + [CommandCmif(0)] + // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + return _inner.GetStandardUserSystemClock(context); + } + + [CommandCmif(1)] + // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + return _inner.GetStandardNetworkSystemClock(context); + } + + [CommandCmif(2)] + // GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock> + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + return _inner.GetStandardSteadyClock(context); + } + + [CommandCmif(3)] + // GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService> + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + return _inner.GetStandardLocalSystemClock(context); + } + + [CommandCmif(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + return _inner.GetEphemeralNetworkSystemClock(context); + } + + [CommandCmif(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle<copy> + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + return _inner.GetSharedMemoryNativeHandle(context); + } + + [CommandCmif(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + if ((_permissions & TimePermissions.SteadyClockWritableMask) == 0) + { + return ResultCode.PermissionDenied; + } + + TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>(); + + // TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds()) + + return ResultCode.Success; + } + + [CommandCmif(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [CommandCmif(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [CommandCmif(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [CommandCmif(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear)) + { + throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!"); + } + + context.ResponseData.Write((int)standardUserSystemClockInitialYear); + + return ResultCode.Success; + } + + [CommandCmif(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + return _inner.IsStandardNetworkSystemClockAccuracySufficient(context); + } + + [CommandCmif(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context); + } + + [CommandCmif(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + return _inner.CalculateMonotonicSystemClockBaseTimePoint(context); + } + + [CommandCmif(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer<nn::time::sf::ClockSnapshot, 0x1a> + public ResultCode GetClockSnapshot(ServiceCtx context) + { + return _inner.GetClockSnapshot(context); + } + + [CommandCmif(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer<nn::time::sf::ClockSnapshot, 0x1a> + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + return _inner.GetClockSnapshotFromSystemClockContext(context); + } + + [CommandCmif(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + return _inner.CalculateStandardUserSystemClockDifferenceByUser(context); + } + + [CommandCmif(501)] // 4.0.0+ + // CalculateSpanBetween(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + return _inner.CalculateSpanBetween(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs new file mode 100644 index 00000000..145d4e3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs @@ -0,0 +1,433 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.Horizon.Common; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:s", TimePermissions.System)] + [Service("time:su", TimePermissions.SystemUpdate)] + class IStaticServiceForPsc : IpcService + { + private TimeManager _timeManager; + private TimePermissions _permissions; + + private int _timeSharedMemoryNativeHandle = 0; + + public IStaticServiceForPsc(ServiceCtx context, TimePermissions permissions) : this(TimeManager.Instance, permissions) {} + + public IStaticServiceForPsc(TimeManager manager, TimePermissions permissions) + { + _permissions = permissions; + _timeManager = manager; + } + + [CommandCmif(0)] + // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardUserSystemClock, + (_permissions & TimePermissions.UserSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock> + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + MakeObject(context, new ISteadyClock(_timeManager.StandardSteadyClock, + (_permissions & TimePermissions.SteadyClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService> + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager, + (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardLocalSystemClock, + (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle<copy> + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + if (_timeSharedMemoryNativeHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.SharedMemory.GetSharedMemory(), out _timeSharedMemoryNativeHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_timeSharedMemoryNativeHandle); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(userClock.IsAutomaticCorrectionEnabled()); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized() || !steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0) + { + return ResultCode.PermissionDenied; + } + + bool autoCorrectionEnabled = context.RequestData.ReadBoolean(); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = userClock.SetAutomaticCorrectionEnabled(tickSource, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _timeManager.SharedMemory.SetAutomaticCorrectionEnabled(autoCorrectionEnabled); + + SteadyClockTimePoint currentTimePoint = userClock.GetSteadyClockCore().GetCurrentTimePoint(tickSource); + + userClock.SetAutomaticCorrectionUpdatedTime(currentTimePoint); + userClock.SignalAutomaticCorrectionEvent(); + } + + return result; + } + + [CommandCmif(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + ITickSource tickSource = context.Device.System.TickSource; + + context.ResponseData.Write(_timeManager.StandardNetworkSystemClock.IsStandardNetworkSystemClockAccuracySufficient(tickSource)); + + return ResultCode.Success; + } + + [CommandCmif(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(userClock.GetAutomaticCorrectionUpdatedTime()); + + return ResultCode.Success; + } + + [CommandCmif(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + + if (!steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + SystemClockContext otherContext = context.RequestData.ReadStruct<SystemClockContext>(); + SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(tickSource); + + ResultCode result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == otherContext.SteadyTimePoint.ClockSourceId) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + long baseTimePoint = otherContext.Offset + currentTimePoint.TimePoint - ticksTimeSpan.ToSeconds(); + + context.ResponseData.Write(baseTimePoint); + + result = ResultCode.Success; + } + + return result; + } + + [CommandCmif(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer<nn::time::sf::ClockSnapshot, 0x1a> + public ResultCode GetClockSnapshot(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ClockSnapshot>()); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _timeManager.StandardUserSystemClock.GetClockContext(tickSource, out SystemClockContext userContext); + + if (result == ResultCode.Success) + { + result = _timeManager.StandardNetworkSystemClock.GetClockContext(tickSource, out SystemClockContext networkContext); + + if (result == ResultCode.Success) + { + result = GetClockSnapshotFromSystemClockContextInternal(tickSource, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + } + } + + return result; + } + + [CommandCmif(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer<nn::time::sf::ClockSnapshot, 0x1a> + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf<ClockSnapshot>()); + + context.RequestData.BaseStream.Position += 7; + + SystemClockContext userContext = context.RequestData.ReadStruct<SystemClockContext>(); + SystemClockContext networkContext = context.RequestData.ReadStruct<SystemClockContext>(); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = GetClockSnapshotFromSystemClockContextInternal(tickSource, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + + return result; + } + + [CommandCmif(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset); + + if (clockSnapshotB.UserContext.SteadyTimePoint.ClockSourceId != clockSnapshotA.UserContext.SteadyTimePoint.ClockSourceId || (clockSnapshotB.IsAutomaticCorrectionEnabled && clockSnapshotA.IsAutomaticCorrectionEnabled)) + { + difference = new TimeSpanType(0); + } + + context.ResponseData.Write(difference.NanoSeconds); + + return ResultCode.Success; + } + + [CommandCmif(501)] // 4.0.0+ + // CalculateSpanBetween(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + + TimeSpanType result; + + ResultCode resultCode = clockSnapshotA.SteadyClockTimePoint.GetSpanBetween(clockSnapshotB.SteadyClockTimePoint, out long timeSpan); + + if (resultCode != ResultCode.Success) + { + resultCode = ResultCode.TimeNotFound; + + if (clockSnapshotA.NetworkTime != 0 && clockSnapshotB.NetworkTime != 0) + { + result = TimeSpanType.FromSeconds(clockSnapshotB.NetworkTime - clockSnapshotA.NetworkTime); + resultCode = ResultCode.Success; + } + else + { + return resultCode; + } + } + else + { + result = TimeSpanType.FromSeconds(timeSpan); + } + + context.ResponseData.Write(result.NanoSeconds); + + return resultCode; + } + + private ResultCode GetClockSnapshotFromSystemClockContextInternal(ITickSource tickSource, SystemClockContext userContext, SystemClockContext networkContext, byte type, out ClockSnapshot clockSnapshot) + { + clockSnapshot = new ClockSnapshot(); + + SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock; + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(tickSource); + + clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled(); + clockSnapshot.UserContext = userContext; + clockSnapshot.NetworkContext = networkContext; + clockSnapshot.SteadyClockTimePoint = currentTimePoint; + + ResultCode result = _timeManager.TimeZone.Manager.GetDeviceLocationName(out string deviceLocationName); + + if (result != ResultCode.Success) + { + return result; + } + + ReadOnlySpan<byte> tzName = Encoding.ASCII.GetBytes(deviceLocationName); + + tzName.CopyTo(clockSnapshot.LocationName); + + result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext); + + if (result == ResultCode.Success) + { + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.UserCalendarTime = userCalendarInfo.Time; + clockSnapshot.UserCalendarAdditionalTime = userCalendarInfo.AdditionalInfo; + + if (ClockSnapshot.GetCurrentTime(out clockSnapshot.NetworkTime, currentTimePoint, clockSnapshot.NetworkContext) != ResultCode.Success) + { + clockSnapshot.NetworkTime = 0; + } + + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.NetworkCalendarTime = networkCalendarInfo.Time; + clockSnapshot.NetworkCalendarAdditionalTime = networkCalendarInfo.AdditionalInfo; + clockSnapshot.Type = type; + + // Probably a version field? + clockSnapshot.Unknown = 0; + } + } + } + + return result; + } + + private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc) + { + Debug.Assert(ipcDesc.Size == (ulong)Unsafe.SizeOf<ClockSnapshot>()); + + byte[] temp = new byte[ipcDesc.Size]; + + context.Memory.Read(ipcDesc.Position, temp); + + using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(temp))) + { + return bufferReader.ReadStruct<ClockSnapshot>(); + } + } + + private void WriteClockSnapshotFromBuffer(ServiceCtx context, IpcRecvListBuffDesc ipcDesc, ClockSnapshot clockSnapshot) + { + MemoryHelper.Write(context.Memory, ipcDesc.Position, clockSnapshot); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs new file mode 100644 index 00000000..6c9c15f1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs @@ -0,0 +1,231 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using Ryujinx.Horizon.Common; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:m")] // 9.0.0+ + class ITimeServiceManager : IpcService + { + private TimeManager _timeManager; + private int _automaticCorrectionEvent; + + public ITimeServiceManager(ServiceCtx context) + { + _timeManager = TimeManager.Instance; + _automaticCorrectionEvent = 0; + } + + [CommandCmif(0)] + // GetUserStaticService() -> object<nn::timesrv::detail::service::IStaticService> + public ResultCode GetUserStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User)); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetAdminStaticService() -> object<nn::timesrv::detail::service::IStaticService> + public ResultCode GetAdminStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin)); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetRepairStaticService() -> object<nn::timesrv::detail::service::IStaticService> + public ResultCode GetRepairStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair)); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // GetManufactureStaticService() -> object<nn::timesrv::detail::service::IStaticService> + public ResultCode GetManufactureStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected) + public ResultCode SetupStandardSteadyClock(ServiceCtx context) + { + UInt128 clockSourceId = context.RequestData.ReadStruct<UInt128>(); + TimeSpanType setupValue = context.RequestData.ReadStruct<TimeSpanType>(); + TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>(); + TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>(); + bool isRtcResetDetected = context.RequestData.ReadBoolean(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardSteadyClock(tickSource, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time) + public ResultCode SetupStandardLocalSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>(); + long posixTime = context.RequestData.ReadInt64(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardLocalSystemClock(tickSource, clockContext, posixTime); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy) + public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>(); + TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct<TimeSpanType>(); + + _timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint) + public ResultCode SetupStandardUserSystemClock(ServiceCtx context) + { + bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean(); + + context.RequestData.BaseStream.Position += 7; + + SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardUserSystemClock(tickSource, isAutomaticCorrectionEnabled, steadyClockTimePoint); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer<nn::time::TimeZoneBinary, 0x21> timezone_binary) + public ResultCode SetupTimeZoneManager(ServiceCtx context) + { + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>(); + uint totalLocationNameCount = context.RequestData.ReadUInt32(); + UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct<UInt128>(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp)) + { + _timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream); + } + + return ResultCode.Success; + } + + [CommandCmif(15)] + // SetupEphemeralNetworkSystemClock() + public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context) + { + _timeManager.SetupEphemeralNetworkSystemClock(); + + return ResultCode.Success; + } + + [CommandCmif(50)] + // Unknown50() -> handle<copy> + public ResultCode Unknown50(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(51)] + // Unknown51() -> handle<copy> + public ResultCode Unknown51(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(52)] + // Unknown52() -> handle<copy> + public ResultCode Unknown52(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(60)] + // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle<copy> + public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context) + { + if (_automaticCorrectionEvent == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset) + public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context) + { + TimeSpanType rtcOffset = context.RequestData.ReadStruct<TimeSpanType>(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetStandardSteadyClockRtcOffset(tickSource, rtcOffset); + + return ResultCode.Success; + } + + [CommandCmif(200)] + // GetAlarmRegistrationEvent() -> handle<copy> + public ResultCode GetAlarmRegistrationEvent(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(201)] + // UpdateSteadyAlarms() + public ResultCode UpdateSteadyAlarms(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(202)] + // TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot) + public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs new file mode 100644 index 00000000..3b042ec0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + public enum ResultCode + { + ModuleId = 116, + ErrorCodeShift = 9, + + Success = 0, + + TimeServiceNotInitialized = (0 << ErrorCodeShift) | ModuleId, + PermissionDenied = (1 << ErrorCodeShift) | ModuleId, + TimeMismatch = (102 << ErrorCodeShift) | ModuleId, + UninitializedClock = (103 << ErrorCodeShift) | ModuleId, + TimeNotFound = (200 << ErrorCodeShift) | ModuleId, + Overflow = (201 << ErrorCodeShift) | ModuleId, + LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, + OutOfRange = (902 << ErrorCodeShift) | ModuleId, + TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, + TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId, + NotImplemented = (990 << ErrorCodeShift) | ModuleId, + NetworkTimeNotAvailable = (1000 << ErrorCodeShift) | ModuleId, + NetworkTimeTaskCanceled = (1003 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs new file mode 100644 index 00000000..97d7884e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs @@ -0,0 +1,155 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Time.Clock; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISteadyClock : IpcService + { + private SteadyClockCore _steadyClock; + private bool _writePermission; + private bool _bypassUninitializedClock; + + public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock) + { + _steadyClock = steadyClock; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + } + + [CommandCmif(0)] + // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint + public ResultCode GetCurrentTimePoint(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(tickSource); + + context.ResponseData.WriteStruct(currentTimePoint); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetTestOffset() -> nn::TimeSpanType + public ResultCode GetTestOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetTestOffset()); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetTestOffset(nn::TimeSpanType) + public ResultCode SetTestOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>(); + + _steadyClock.SetTestOffset(testOffset); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 2.0.0+ + // GetRtcValue() -> u64 + public ResultCode GetRtcValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [CommandCmif(101)] // 2.0.0+ + // IsRtcResetDetected() -> bool + public ResultCode IsRtcResetDetected(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(_steadyClock.IsRtcResetDetected()); + + return ResultCode.Success; + } + + [CommandCmif(102)] // 2.0.0+ + // GetSetupResultValue() -> u32 + public ResultCode GetSetupResultValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue()); + + return ResultCode.Success; + } + + [CommandCmif(200)] // 3.0.0+ + // GetInternalOffset() -> nn::TimeSpanType + public ResultCode GetInternalOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset()); + + return ResultCode.Success; + } + + [CommandCmif(201)] // 3.0.0-3.0.2 + // SetInternalOffset(nn::TimeSpanType) + public ResultCode SetInternalOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>(); + + _steadyClock.SetInternalOffset(internalOffset); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs new file mode 100644 index 00000000..3cd0a4a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs @@ -0,0 +1,131 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISystemClock : IpcService + { + private SystemClockCore _clockCore; + private bool _writePermission; + private bool _bypassUninitializedClock; + private int _operationEventReadableHandle; + + public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock) + { + _clockCore = clockCore; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + _operationEventReadableHandle = 0; + } + + [CommandCmif(0)] + // GetCurrentTime() -> nn::time::PosixTime + public ResultCode GetCurrentTime(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _clockCore.GetCurrentTime(tickSource, out long posixTime); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(posixTime); + } + + return result; + } + + [CommandCmif(1)] + // SetCurrentTime(nn::time::PosixTime) + public ResultCode SetCurrentTime(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + long posixTime = context.RequestData.ReadInt64(); + + ITickSource tickSource = context.Device.System.TickSource; + + return _clockCore.SetCurrentTime(tickSource, posixTime); + } + + [CommandCmif(2)] + // GetClockContext() -> nn::time::SystemClockContext + public ResultCode GetSystemClockContext(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _clockCore.GetClockContext(tickSource, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(clockContext); + } + + return result; + } + + [CommandCmif(3)] + // SetClockContext(nn::time::SystemClockContext) + public ResultCode SetSystemClockContext(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>(); + + ResultCode result = _clockCore.SetSystemClockContext(clockContext); + + return result; + } + + [CommandCmif(4)] // 9.0.0+ + // GetOperationEventReadableHandle() -> handle<copy> + public ResultCode GetOperationEventReadableHandle(ServiceCtx context) + { + if (_operationEventReadableHandle == 0) + { + KEvent kEvent = new KEvent(context.Device.System.KernelContext); + + _clockCore.RegisterOperationEvent(kEvent.WritableEvent); + + if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs new file mode 100644 index 00000000..96a7e604 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs @@ -0,0 +1,142 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForGlue : IpcService + { + private TimeZoneContentManager _timeZoneContentManager; + private ITimeZoneServiceForPsc _inner; + private bool _writePermission; + + public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission) + { + _timeZoneContentManager = timeZoneContentManager; + _writePermission = writePermission; + _inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission); + } + + [CommandCmif(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + return _inner.GetDeviceLocationName(context); + } + + [CommandCmif(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + return _timeZoneContentManager.SetDeviceLocationName(locationName); + } + + [CommandCmif(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + return _inner.GetTotalLocationNameCount(context); + } + + [CommandCmif(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + uint index = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24); + + if (errorCode == 0) + { + uint offset = 0; + + foreach (string locationName in locationNameArray) + { + int padding = 0x24 - locationName.Length; + + if (padding < 0) + { + return ResultCode.LocationNameTooLong; + } + + context.Memory.Write(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName)); + MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + (ulong)locationName.Length, padding); + + offset += 0x24; + } + + context.ResponseData.Write((uint)locationNameArray.Length); + } + + return errorCode; + } + + [CommandCmif(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16> + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + using (WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf<TimeZoneRule>())) + { + ref TimeZoneRule rules = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0]; + + return _timeZoneContentManager.LoadTimeZoneRule(ref rules, locationName); + } + } + + [CommandCmif(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + return _inner.ToCalendarTime(context); + } + + [CommandCmif(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + return _inner.ToCalendarTimeWithMyRule(context); + } + + [CommandCmif(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>) + public ResultCode ToPosixTime(ServiceCtx context) + { + return _inner.ToPosixTime(context); + } + + [CommandCmif(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + return _inner.ToPosixTimeWithMyRule(context); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs new file mode 100644 index 00000000..3c9ac71f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs @@ -0,0 +1,303 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForPsc : IpcService + { + private TimeZoneManager _timeZoneManager; + private bool _writePermission; + + public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission) + { + _timeZoneManager = timeZoneManager; + _writePermission = writePermission; + } + + [CommandCmif(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + } + + return result; + } + + [CommandCmif(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + return ResultCode.NotImplemented; + } + + [CommandCmif(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(totalLocationNameCount); + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16> + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(5)] // 2.0.0+ + // GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion + public ResultCode GetTimeZoneRuleVersion(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(timeZoneRuleVersion); + } + + return result; + } + + [CommandCmif(6)] // 5.0.0+ + // GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint) + public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + + // Skip padding + context.ResponseData.BaseStream.Position += 0x4; + + context.ResponseData.WriteStruct(timeZoneUpdateTimePoint); + } + } + + return result; + } + + [CommandCmif(7)] // 9.0.0+ + // SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary) + public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + ResultCode result; + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp)) + { + result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + } + + return result; + } + + [CommandCmif(8)] // 9.0.0+ + // ParseTimeZoneBinary(buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary) -> buffer<nn::time::TimeZoneRule, 0x16> + public ResultCode ParseTimeZoneBinary(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + ulong timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size; + + if (timeZoneRuleBufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ResultCode result; + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp)) + { + using (WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf<TimeZoneRule>())) + { + ref TimeZoneRule rule = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0]; + + result = _timeZoneManager.ParseTimeZoneRuleBinary(ref rule, timeZoneBinaryStream); + } + } + + return result; + } + + [CommandCmif(20)] // 9.0.0+ + // GetDeviceLocationNameOperationEventReadableHandle() -> handle<copy> + public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(bufferPosition, (int)bufferSize)); + + ResultCode resultCode = _timeZoneManager.ToCalendarTime(in rules[0], posixTime, out CalendarInfo calendar); + + if (resultCode == 0) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [CommandCmif(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + + ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [CommandCmif(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>) + public ResultCode ToPosixTime(ServiceCtx context) + { + ulong inBufferPosition = context.Request.SendBuff[0].Position; + ulong inBufferSize = context.Request.SendBuff[0].Size; + + CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>(); + + if (inBufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(inBufferPosition, (int)inBufferSize)); + + ResultCode resultCode = _timeZoneManager.ToPosixTime(in rules[0], calendarTime, out long posixTime); + + if (resultCode == ResultCode.Success) + { + ulong outBufferPosition = context.Request.RecvListBuff[0].Position; + ulong outBufferSize = context.Request.RecvListBuff[0].Size; + + context.Memory.Write(outBufferPosition, posixTime); + context.ResponseData.Write(1); + } + + return resultCode; + } + + [CommandCmif(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>(); + + ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime); + + if (resultCode == ResultCode.Success) + { + ulong outBufferPosition = context.Request.RecvListBuff[0].Position; + ulong outBufferSize = context.Request.RecvListBuff[0].Size; + + context.Memory.Write(outBufferPosition, posixTime); + + // There could be only one result on one calendar as leap seconds aren't supported. + context.ResponseData.Write(1); + } + + return resultCode; + } + + private void WriteLocationName(ServiceCtx context, string locationName) + { + char[] locationNameArray = locationName.ToCharArray(); + + int padding = 0x24 - locationNameArray.Length; + + Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)"); + + context.ResponseData.Write(locationNameArray); + + for (int index = 0; index < padding; index++) + { + context.ResponseData.Write((byte)0); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs new file mode 100644 index 00000000..e3b65f2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs @@ -0,0 +1,182 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeManager + { + private static TimeManager _instance; + + public static TimeManager Instance + { + get + { + if (_instance == null) + { + _instance = new TimeManager(); + } + + return _instance; + } + } + + public StandardSteadyClockCore StandardSteadyClock { get; } + public TickBasedSteadyClockCore TickBasedSteadyClock { get; } + public StandardLocalSystemClockCore StandardLocalSystemClock { get; } + public StandardNetworkSystemClockCore StandardNetworkSystemClock { get; } + public StandardUserSystemClockCore StandardUserSystemClock { get; } + public TimeZoneContentManager TimeZone { get; } + public EphemeralNetworkSystemClockCore EphemeralNetworkSystemClock { get; } + public TimeSharedMemory SharedMemory { get; } + public LocalSystemClockContextWriter LocalClockContextWriter { get; } + public NetworkSystemClockContextWriter NetworkClockContextWriter { get; } + public EphemeralNetworkSystemClockContextWriter EphemeralClockContextWriter { get; } + + // TODO: 9.0.0+ power states and alarms + + public TimeManager() + { + StandardSteadyClock = new StandardSteadyClockCore(); + TickBasedSteadyClock = new TickBasedSteadyClockCore(); + StandardLocalSystemClock = new StandardLocalSystemClockCore(StandardSteadyClock); + StandardNetworkSystemClock = new StandardNetworkSystemClockCore(StandardSteadyClock); + StandardUserSystemClock = new StandardUserSystemClockCore(StandardLocalSystemClock, StandardNetworkSystemClock); + TimeZone = new TimeZoneContentManager(); + EphemeralNetworkSystemClock = new EphemeralNetworkSystemClockCore(TickBasedSteadyClock); + SharedMemory = new TimeSharedMemory(); + LocalClockContextWriter = new LocalSystemClockContextWriter(SharedMemory); + NetworkClockContextWriter = new NetworkSystemClockContextWriter(SharedMemory); + EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter(); + } + + public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, SharedMemoryStorage timeSharedMemoryStorage, int timeSharedMemorySize) + { + SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryStorage, timeSharedMemorySize); + + // Here we use system on purpose as device. System isn't initialized at this point. + StandardUserSystemClock.CreateAutomaticCorrectionEvent(system); + } + + public void InitializeTimeZone(Switch device) + { + TimeZone.Initialize(this, device); + } + + public void SetupStandardSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + SetupInternalStandardSteadyClock(clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(tickSource); + + SharedMemory.SetupStandardSteadyClock(tickSource, clockSourceId, currentTimePoint); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + private void SetupInternalStandardSteadyClock(UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + StandardSteadyClock.SetClockSourceId(clockSourceId); + StandardSteadyClock.SetSetupValue(setupValue); + StandardSteadyClock.SetInternalOffset(internalOffset); + StandardSteadyClock.SetTestOffset(testOffset); + + if (isRtcResetDetected) + { + StandardSteadyClock.SetRtcReset(); + } + + StandardSteadyClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardLocalSystemClock(ITickSource tickSource, SystemClockContext clockContext, long posixTime) + { + StandardLocalSystemClock.SetUpdateCallbackInstance(LocalClockContextWriter); + + SteadyClockTimePoint currentTimePoint = StandardLocalSystemClock.GetSteadyClockCore().GetCurrentTimePoint(tickSource); + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + StandardLocalSystemClock.SetSystemClockContext(clockContext); + } + else + { + if (StandardLocalSystemClock.SetCurrentTime(tickSource, posixTime) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set current local time"); + } + } + + StandardLocalSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardNetworkSystemClock(SystemClockContext clockContext, TimeSpanType sufficientAccuracy) + { + StandardNetworkSystemClock.SetUpdateCallbackInstance(NetworkClockContextWriter); + + if (StandardNetworkSystemClock.SetSystemClockContext(clockContext) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set network SystemClockContext"); + } + + StandardNetworkSystemClock.SetStandardNetworkClockSufficientAccuracy(sufficientAccuracy); + StandardNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupTimeZoneManager(string locationName, SteadyClockTimePoint timeZoneUpdatedTimePoint, uint totalLocationNameCount, UInt128 timeZoneRuleVersion, Stream timeZoneBinaryStream) + { + if (TimeZone.Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set DeviceLocationName with a given TimeZoneBinary"); + } + + TimeZone.Manager.SetUpdatedTime(timeZoneUpdatedTimePoint, true); + TimeZone.Manager.SetTotalLocationNameCount(totalLocationNameCount); + TimeZone.Manager.SetTimeZoneRuleVersion(timeZoneRuleVersion); + TimeZone.Manager.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupEphemeralNetworkSystemClock() + { + EphemeralNetworkSystemClock.SetUpdateCallbackInstance(EphemeralClockContextWriter); + EphemeralNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardUserSystemClock(ITickSource tickSource, bool isAutomaticCorrectionEnabled, SteadyClockTimePoint steadyClockTimePoint) + { + if (StandardUserSystemClock.SetAutomaticCorrectionEnabled(tickSource, isAutomaticCorrectionEnabled) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set automatic user time correction state"); + } + + StandardUserSystemClock.SetAutomaticCorrectionUpdatedTime(steadyClockTimePoint); + StandardUserSystemClock.MarkInitialized(); + + SharedMemory.SetAutomaticCorrectionEnabled(isAutomaticCorrectionEnabled); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetStandardSteadyClockRtcOffset(ITickSource tickSource, TimeSpanType rtcOffset) + { + StandardSteadyClock.SetSetupValue(rtcOffset); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(tickSource); + + SharedMemory.SetSteadyClockRawTimePoint(tickSource, currentTimePoint); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs new file mode 100644 index 00000000..7063290b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs @@ -0,0 +1,114 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.Types; +using Ryujinx.HLE.Utilities; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeSharedMemory + { + private Switch _device; + private KSharedMemory _sharedMemory; + private SharedMemoryStorage _timeSharedMemoryStorage; + private int _timeSharedMemorySize; + + private const uint SteadyClockContextOffset = 0x00; + private const uint LocalSystemClockContextOffset = 0x38; + private const uint NetworkSystemClockContextOffset = 0x80; + private const uint AutomaticCorrectionEnabledOffset = 0xC8; + + public void Initialize(Switch device, KSharedMemory sharedMemory, SharedMemoryStorage timeSharedMemoryStorage, int timeSharedMemorySize) + { + _device = device; + _sharedMemory = sharedMemory; + _timeSharedMemoryStorage = timeSharedMemoryStorage; + _timeSharedMemorySize = timeSharedMemorySize; + + // Clean the shared memory + timeSharedMemoryStorage.ZeroFill(); + } + + public KSharedMemory GetSharedMemory() + { + return _sharedMemory; + } + + public void SetupStandardSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType currentTimePoint) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + SteadyClockContext context = new SteadyClockContext + { + InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds), + ClockSourceId = clockSourceId + }; + + WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context); + } + + public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled) + { + // We convert the bool to byte here as a bool in C# takes 4 bytes... + WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled)); + } + + public void SetSteadyClockRawTimePoint(ITickSource tickSource, TimeSpanType currentTimePoint) + { + SteadyClockContext context = ReadObjectFromSharedMemory<SteadyClockContext>(SteadyClockContextOffset, 4); + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds); + + WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context); + } + + public void UpdateLocalSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context); + } + + public void UpdateNetworkSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context); + } + + private T ReadObjectFromSharedMemory<T>(ulong offset, ulong padding) where T : unmanaged + { + T result; + uint index; + uint possiblyNewIndex; + + do + { + index = _timeSharedMemoryStorage.GetRef<uint>(offset); + + ulong objectOffset = offset + 4 + padding + (ulong)((index & 1) * Unsafe.SizeOf<T>()); + + result = _timeSharedMemoryStorage.GetRef<T>(objectOffset); + + Thread.MemoryBarrier(); + + possiblyNewIndex = _device.Memory.Read<uint>(offset); + } while (index != possiblyNewIndex); + + return result; + } + + private void WriteObjectToSharedMemory<T>(ulong offset, ulong padding, T value) where T : unmanaged + { + uint newIndex = _timeSharedMemoryStorage.GetRef<uint>(offset) + 1; + + ulong objectOffset = offset + 4 + padding + (ulong)((newIndex & 1) * Unsafe.SizeOf<T>()); + + _timeSharedMemoryStorage.GetRef<T>(objectOffset) = value; + + Thread.MemoryBarrier(); + + _timeSharedMemoryStorage.GetRef<uint>(offset) = newIndex; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs new file mode 100644 index 00000000..f7477e97 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs @@ -0,0 +1,1703 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.Utilities; +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + public class TimeZone + { + private const int TimeTypeSize = 8; + private const int EpochYear = 1970; + private const int YearBase = 1900; + private const int EpochWeekDay = 4; + private const int SecondsPerMinute = 60; + private const int MinutesPerHour = 60; + private const int HoursPerDays = 24; + private const int DaysPerWekk = 7; + private const int DaysPerNYear = 365; + private const int DaysPerLYear = 366; + private const int MonthsPerYear = 12; + private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour; + private const int SecondsPerDay = SecondsPerHour * HoursPerDays; + + private const int YearsPerRepeat = 400; + private const long AverageSecondsPerYear = 31556952; + private const long SecondsPerRepeat = YearsPerRepeat * AverageSecondsPerYear; + + private static readonly int[] YearLengths = { DaysPerNYear, DaysPerLYear }; + private static readonly int[][] MonthsLengths = new int[][] + { + new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + + private static ReadOnlySpan<byte> TimeZoneDefaultRule => ",M4.1.0,M10.5.0"u8; + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)] + private struct CalendarTimeInternal + { + // NOTE: On the IPC side this is supposed to be a 16 bits value but internally this need to be a 64 bits value for ToPosixTime. + public long Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + + public int CompareTo(CalendarTimeInternal other) + { + if (Year != other.Year) + { + if (Year < other.Year) + { + return -1; + } + + return 1; + } + + if (Month != other.Month) + { + return Month - other.Month; + } + + if (Day != other.Day) + { + return Day - other.Day; + } + + if (Hour != other.Hour) + { + return Hour - other.Hour; + } + + if (Minute != other.Minute) + { + return Minute - other.Minute; + } + + if (Second != other.Second) + { + return Second - other.Second; + } + + return 0; + } + } + + private enum RuleType + { + JulianDay, + DayOfYear, + MonthNthDayOfWeek + } + + private struct Rule + { + public RuleType Type; + public int Day; + public int Week; + public int Month; + public int TransitionTime; + } + + private static int Detzcode32(ReadOnlySpan<byte> bytes) + { + return BinaryPrimitives.ReadInt32BigEndian(bytes); + } + + private static int Detzcode32(int value) + { + if (BitConverter.IsLittleEndian) + { + return BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + private static long Detzcode64(ReadOnlySpan<byte> bytes) + { + return BinaryPrimitives.ReadInt64BigEndian(bytes); + } + + private static bool DifferByRepeat(long t1, long t0) + { + return (t1 - t0) == SecondsPerRepeat; + } + + private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex) + { + if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount) + { + return false; + } + + TimeTypeInfo a = outRules.Ttis[aIndex]; + TimeTypeInfo b = outRules.Ttis[bIndex]; + + return a.GmtOffset == b.GmtOffset && + a.IsDaySavingTime == b.IsDaySavingTime && + a.IsStandardTimeDaylight == b.IsStandardTimeDaylight && + a.IsGMT == b.IsGMT && + StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0; + } + + private static int GetQZName(ReadOnlySpan<byte> name, int namePosition, char delimiter) + { + int i = namePosition; + + while (name[i] != '\0' && name[i] != delimiter) + { + i++; + } + + return i; + } + + private static int GetTZName(ReadOnlySpan<byte> name, int namePosition) + { + int i = namePosition; + + char c; + + while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+') + { + i++; + } + + return i; + } + + private static bool GetNum(ReadOnlySpan<byte> name, ref int namePosition, out int num, int min, int max) + { + num = 0; + + if (namePosition >= name.Length) + { + return false; + } + + char c = (char)name[namePosition]; + + if (!char.IsDigit(c)) + { + return false; + } + + do + { + num = num * 10 + (c - '0'); + if (num > max) + { + return false; + } + + if (++namePosition >= name.Length) + { + return false; + } + + c = (char)name[namePosition]; + } + while (char.IsDigit(c)); + + if (num < min) + { + return false; + } + + return true; + } + + private static bool GetSeconds(ReadOnlySpan<byte> name, ref int namePosition, out int seconds) + { + seconds = 0; + + + bool isValid = GetNum(name, ref namePosition, out int num, 0, HoursPerDays * DaysPerWekk - 1); + if (!isValid) + { + return false; + } + + seconds = num * SecondsPerHour; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1); + if (!isValid) + { + return false; + } + + seconds += num * SecondsPerMinute; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, SecondsPerMinute); + if (!isValid) + { + return false; + } + + seconds += num; + } + } + return true; + } + + private static bool GetOffset(ReadOnlySpan<byte> name, ref int namePosition, ref int offset) + { + bool isNegative = false; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == '-') + { + isNegative = true; + namePosition++; + } + else if (name[namePosition] == '+') + { + namePosition++; + } + + if (namePosition >= name.Length) + { + return false; + } + + bool isValid = GetSeconds(name, ref namePosition, out offset); + if (!isValid) + { + return false; + } + + if (isNegative) + { + offset = -offset; + } + + return true; + } + + private static bool GetRule(ReadOnlySpan<byte> name, ref int namePosition, out Rule rule) + { + rule = new Rule(); + + bool isValid = false; + + if (name[namePosition] == 'J') + { + namePosition++; + + rule.Type = RuleType.JulianDay; + isValid = GetNum(name, ref namePosition, out rule.Day, 1, DaysPerNYear); + } + else if (name[namePosition] == 'M') + { + namePosition++; + + rule.Type = RuleType.MonthNthDayOfWeek; + isValid = GetNum(name, ref namePosition, out rule.Month, 1, MonthsPerYear); + + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Week, 1, 5); + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1); + } + else if (char.IsDigit((char)name[namePosition])) + { + rule.Type = RuleType.DayOfYear; + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1); + } + else + { + return false; + } + + if (!isValid) + { + return false; + } + + if (name[namePosition] == '/') + { + namePosition++; + return GetOffset(name, ref namePosition, ref rule.TransitionTime); + } + else + { + rule.TransitionTime = 2 * SecondsPerHour; + } + + return true; + } + + private static int IsLeap(int year) + { + if (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)) + { + return 1; + } + + return 0; + } + + private static bool ParsePosixName(ReadOnlySpan<byte> name, ref TimeZoneRule outRules, bool lastDitch) + { + outRules = new TimeZoneRule(); + + int stdLen; + + ReadOnlySpan<byte> stdName = name; + int namePosition = 0; + int stdOffset = 0; + + if (lastDitch) + { + stdLen = 3; + namePosition += stdLen; + } + else + { + if (name[namePosition] == '<') + { + namePosition++; + + stdName = name.Slice(namePosition); + + int stdNamePosition = namePosition; + + namePosition = GetQZName(name, namePosition, '>'); + + if (name[namePosition] != '>') + { + return false; + } + + stdLen = namePosition - stdNamePosition; + namePosition++; + } + else + { + namePosition = GetTZName(name, namePosition); + stdLen = namePosition; + } + + if (stdLen == 0) + { + return false; + } + + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref stdOffset); + + if (!isValid) + { + return false; + } + } + + int charCount = stdLen + 1; + int destLen = 0; + int dstOffset = 0; + + ReadOnlySpan<byte> destName = name.Slice(namePosition); + + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0') + { + if (name[namePosition] == '<') + { + destName = name.Slice(++namePosition); + int destNamePosition = namePosition; + + namePosition = GetQZName(name.ToArray(), namePosition, '>'); + + if (name[namePosition] != '>') + { + return false; + } + + destLen = namePosition - destNamePosition; + namePosition++; + } + else + { + destName = name.Slice(namePosition); + namePosition = GetTZName(name, namePosition); + destLen = namePosition; + } + + if (destLen == 0) + { + return false; + } + + charCount += destLen + 1; + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0' && name[namePosition] != ',' && name[namePosition] != ';') + { + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref dstOffset); + + if (!isValid) + { + return false; + } + } + else + { + dstOffset = stdOffset - SecondsPerHour; + } + + if (name[namePosition] == '\0') + { + name = TimeZoneDefaultRule; + namePosition = 0; + } + + if (name[namePosition] == ',' || name[namePosition] == ';') + { + namePosition++; + + bool IsRuleValid = GetRule(name, ref namePosition, out Rule start); + if (!IsRuleValid) + { + return false; + } + + if (name[namePosition++] != ',') + { + return false; + } + + IsRuleValid = GetRule(name, ref namePosition, out Rule end); + if (!IsRuleValid) + { + return false; + } + + if (name[namePosition] != '\0') + { + return false; + } + + outRules.TypeCount = 2; + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1 + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + + outRules.DefaultType = 0; + + int timeCount = 0; + long janFirst = 0; + int janOffset = 0; + int yearBegining = EpochYear; + + do + { + int yearSeconds = YearLengths[IsLeap(yearBegining - 1)] * SecondsPerDay; + yearBegining--; + if (IncrementOverflow64(ref janFirst, -yearSeconds)) + { + janOffset = -yearSeconds; + break; + } + } + while (EpochYear - YearsPerRepeat / 2 < yearBegining); + + int yearLimit = yearBegining + YearsPerRepeat + 1; + int year; + for (year = yearBegining; year < yearLimit; year++) + { + int startTime = TransitionTime(year, start, stdOffset); + int endTime = TransitionTime(year, end, dstOffset); + + int yearSeconds = YearLengths[IsLeap(year)] * SecondsPerDay; + + bool isReversed = endTime < startTime; + if (isReversed) + { + int swap = startTime; + + startTime = endTime; + endTime = swap; + } + + if (isReversed || (startTime < endTime && (endTime - startTime < (yearSeconds + (stdOffset - dstOffset))))) + { + if (TzMaxTimes - 2 < timeCount) + { + break; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + startTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)1 : (byte)0; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 1 : 0; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + endTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)0 : (byte)1; + yearLimit = year + YearsPerRepeat + 1; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 0 : 1; + } + } + + if (IncrementOverflow64(ref janFirst, janOffset + yearSeconds)) + { + break; + } + + janOffset = 0; + } + + outRules.TimeCount = timeCount; + + // There is no time variation, this is then a perpetual DST rule + if (timeCount == 0) + { + outRules.TypeCount = 1; + } + else if (YearsPerRepeat < year - yearBegining) + { + outRules.GoBack = true; + outRules.GoAhead = true; + } + } + else + { + if (name[namePosition] == '\0') + { + return false; + } + + long theirStdOffset = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsStandardTimeDaylight) + { + theirStdOffset = -outRules.Ttis[j].GmtOffset; + } + } + + long theirDstOffset = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsDaySavingTime) + { + theirDstOffset = -outRules.Ttis[j].GmtOffset; + } + } + + bool isDaySavingTime = false; + long theirOffset = theirStdOffset; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + outRules.Types[i] = outRules.Ttis[j].IsDaySavingTime ? (byte)1 : (byte)0; + if (!outRules.Ttis[j].IsGMT) + { + if (isDaySavingTime && !outRules.Ttis[j].IsStandardTimeDaylight) + { + outRules.Ats[i] += dstOffset - theirStdOffset; + } + else + { + outRules.Ats[i] += stdOffset - theirStdOffset; + } + } + + theirOffset = -outRules.Ttis[j].GmtOffset; + if (outRules.Ttis[j].IsDaySavingTime) + { + theirDstOffset = theirOffset; + } + else + { + theirStdOffset = theirOffset; + } + } + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1 + }; + + outRules.TypeCount = 2; + outRules.DefaultType = 0; + } + } + else + { + // default is perpetual standard time + outRules.TypeCount = 1; + outRules.TimeCount = 0; + outRules.DefaultType = 0; + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0 + }; + } + + outRules.CharCount = charCount; + + int charsPosition = 0; + + for (int i = 0; i < stdLen; i++) + { + outRules.Chars[i] = stdName[i]; + } + + charsPosition += stdLen; + outRules.Chars[charsPosition++] = 0; + + if (destLen != 0) + { + for (int i = 0; i < destLen; i++) + { + outRules.Chars[charsPosition + i] = destName[i]; + } + outRules.Chars[charsPosition + destLen] = 0; + } + + return true; + } + + private static int TransitionTime(int year, Rule rule, int offset) + { + int leapYear = IsLeap(year); + + int value; + switch (rule.Type) + { + case RuleType.JulianDay: + value = (rule.Day - 1) * SecondsPerDay; + if (leapYear == 1 && rule.Day >= 60) + { + value += SecondsPerDay; + } + break; + + case RuleType.DayOfYear: + value = rule.Day * SecondsPerDay; + break; + + case RuleType.MonthNthDayOfWeek: + // Here we use Zeller's Congruence to get the day of week of the first month. + + int m1 = (rule.Month + 9) % 12 + 1; + int yy0 = (rule.Month <= 2) ? (year - 1) : year; + int yy1 = yy0 / 100; + int yy2 = yy0 % 100; + + int dayOfWeek = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWekk; + } + + // Get the zero origin + int d = rule.Day - dayOfWeek; + + if (d < 0) + { + d += DaysPerWekk; + } + + for (int i = 1; i < rule.Week; i++) + { + if (d + DaysPerWekk >= MonthsLengths[leapYear][rule.Month - 1]) + { + break; + } + + d += DaysPerWekk; + } + + value = d * SecondsPerDay; + for (int i = 0; i < rule.Month - 1; i++) + { + value += MonthsLengths[leapYear][i] * SecondsPerDay; + } + + break; + default: + throw new NotImplementedException("Unknown time transition!"); + } + + return value + rule.TransitionTime + offset; + } + + private static bool NormalizeOverflow32(ref int ip, ref int unit, int baseValue) + { + int delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow32(ref ip, delta); + } + + private static bool NormalizeOverflow64(ref long ip, ref long unit, long baseValue) + { + long delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow64(ref ip, delta); + } + + private static bool IncrementOverflow32(ref int time, int j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + private static bool IncrementOverflow64(ref long time, long j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + internal static bool ParsePosixName(string name, ref TimeZoneRule outRules) + { + return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false); + } + + internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData) + { + outRules = new TimeZoneRule(); + + BinaryReader reader = new BinaryReader(inputData); + + long streamLength = reader.BaseStream.Length; + + if (streamLength < Unsafe.SizeOf<TzifHeader>()) + { + return false; + } + + TzifHeader header = reader.ReadStruct<TzifHeader>(); + + streamLength -= Unsafe.SizeOf<TzifHeader>(); + + int ttisGMTCount = Detzcode32(header.TtisGMTCount); + int ttisSTDCount = Detzcode32(header.TtisSTDCount); + int leapCount = Detzcode32(header.LeapCount); + int timeCount = Detzcode32(header.TimeCount); + int typeCount = Detzcode32(header.TypeCount); + int charCount = Detzcode32(header.CharCount); + + if (!(0 <= leapCount + && leapCount < TzMaxLeaps + && 0 < typeCount + && typeCount < TzMaxTypes + && 0 <= timeCount + && timeCount < TzMaxTimes + && 0 <= charCount + && charCount < TzMaxChars + && (ttisSTDCount == typeCount || ttisSTDCount == 0) + && (ttisGMTCount == typeCount || ttisGMTCount == 0))) + { + return false; + } + + + if (streamLength < (timeCount * TimeTypeSize + + timeCount + + typeCount * 6 + + charCount + + leapCount * (TimeTypeSize + 4) + + ttisSTDCount + + ttisGMTCount)) + { + return false; + } + + outRules.TimeCount = timeCount; + outRules.TypeCount = typeCount; + outRules.CharCount = charCount; + + byte[] workBuffer = StreamUtils.StreamToBytes(inputData); + + timeCount = 0; + + { + Span<byte> p = workBuffer; + for (int i = 0; i < outRules.TimeCount; i++) + { + long at = Detzcode64(p); + outRules.Types[i] = 1; + + if (timeCount != 0 && at <= outRules.Ats[timeCount - 1]) + { + if (at < outRules.Ats[timeCount - 1]) + { + return false; + } + + outRules.Types[i - 1] = 0; + timeCount--; + } + + outRules.Ats[timeCount++] = at; + + p = p[TimeTypeSize..]; + } + + timeCount = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + byte type = p[0]; + p = p[1..]; + + if (outRules.TypeCount <= type) + { + return false; + } + + if (outRules.Types[i] != 0) + { + outRules.Types[timeCount++] = type; + } + } + + outRules.TimeCount = timeCount; + + for (int i = 0; i < outRules.TypeCount; i++) + { + TimeTypeInfo ttis = outRules.Ttis[i]; + ttis.GmtOffset = Detzcode32(p); + p = p[sizeof(int)..]; + + if (p[0] >= 2) + { + return false; + } + + ttis.IsDaySavingTime = p[0] != 0; + p = p[1..]; + + int abbreviationListIndex = p[0]; + p = p[1..]; + + if (abbreviationListIndex >= outRules.CharCount) + { + return false; + } + + ttis.AbbreviationListIndex = abbreviationListIndex; + + outRules.Ttis[i] = ttis; + } + + p[..outRules.CharCount].CopyTo(outRules.Chars); + + p = p[outRules.CharCount..]; + outRules.Chars[outRules.CharCount] = 0; + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsStandardTimeDaylight = false; + } + else + { + if (p[0] >= 2) + { + return false; + } + + outRules.Ttis[i].IsStandardTimeDaylight = p[0] != 0; + p = p[1..]; + } + } + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsGMT = false; + } + else + { + if (p[0] >= 2) + { + return false; + } + + outRules.Ttis[i].IsGMT = p[0] != 0; + p = p[1..]; + } + + } + + long position = (workBuffer.Length - p.Length); + long nRead = streamLength - position; + + if (nRead < 0) + { + return false; + } + + // Nintendo abort in case of a TzIf file with a POSIX TZ Name too long to fit inside a TimeZoneRule. + // As it's impossible in normal usage to achive this, we also force a crash. + if (nRead > (TzNameMax + 1)) + { + throw new InvalidOperationException(); + } + + byte[] tempName = new byte[TzNameMax + 1]; + Array.Copy(workBuffer, position, tempName, 0, nRead); + + if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes) + { + tempName[nRead - 1] = 0; + + byte[] name = new byte[TzNameMax]; + Array.Copy(tempName, 1, name, 0, nRead - 1); + + Box<TimeZoneRule> tempRulesBox = new Box<TimeZoneRule>(); + ref TimeZoneRule tempRules = ref tempRulesBox.Data; + + if (ParsePosixName(name, ref tempRulesBox.Data, false)) + { + int abbreviationCount = 0; + charCount = outRules.CharCount; + + Span<byte> chars = outRules.Chars; + + for (int i = 0; i < tempRules.TypeCount; i++) + { + ReadOnlySpan<byte> tempChars = tempRules.Chars; + ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..]; + + int j; + + for (j = 0; j < charCount; j++) + { + if (StringUtils.CompareCStr(chars[j..], tempAbbreviation) == 0) + { + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + break; + } + } + + if (j >= charCount) + { + int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation); + if (j + abbreviationLength < TzMaxChars) + { + for (int x = 0; x < abbreviationLength; x++) + { + chars[j + x] = tempAbbreviation[x]; + } + + charCount = j + abbreviationLength + 1; + + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + } + } + } + + if (abbreviationCount == tempRules.TypeCount) + { + outRules.CharCount = charCount; + + // Remove trailing + while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2])) + { + outRules.TimeCount--; + } + + int i; + + for (i = 0; i < tempRules.TimeCount; i++) + { + if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i]) + { + break; + } + } + + while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes) + { + outRules.Ats[outRules.TimeCount] = tempRules.Ats[i]; + outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]); + + outRules.TimeCount++; + i++; + } + + for (i = 0; i < tempRules.TypeCount; i++) + { + outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i]; + } + } + } + } + + if (outRules.TypeCount == 0) + { + return false; + } + + if (outRules.TimeCount > 1) + { + for (int i = 1; i < outRules.TimeCount; i++) + { + if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0])) + { + outRules.GoBack = true; + break; + } + } + + for (int i = outRules.TimeCount - 2; i >= 0; i--) + { + if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i])) + { + outRules.GoAhead = true; + break; + } + } + } + + int defaultType; + + for (defaultType = 0; defaultType < outRules.TimeCount; defaultType++) + { + if (outRules.Types[defaultType] == 0) + { + break; + } + } + + defaultType = defaultType < outRules.TimeCount ? -1 : 0; + + if (defaultType < 0 && outRules.TimeCount > 0 && outRules.Ttis[outRules.Types[0]].IsDaySavingTime) + { + defaultType = outRules.Types[0]; + while (--defaultType >= 0) + { + if (!outRules.Ttis[defaultType].IsDaySavingTime) + { + break; + } + } + } + + if (defaultType < 0) + { + defaultType = 0; + while (outRules.Ttis[defaultType].IsDaySavingTime) + { + if (++defaultType >= outRules.TypeCount) + { + defaultType = 0; + break; + } + } + } + + outRules.DefaultType = defaultType; + } + + return true; + } + + private static long GetLeapDaysNotNeg(long year) + { + return year / 4 - year / 100 + year / 400; + } + + private static long GetLeapDays(long year) + { + if (year < 0) + { + return -1 - GetLeapDaysNotNeg(-1 - year); + } + else + { + return GetLeapDaysNotNeg(year); + } + } + + private static ResultCode CreateCalendarTime(long time, int gmtOffset, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + long year = EpochYear; + long timeDays = time / SecondsPerDay; + long remainingSeconds = time % SecondsPerDay; + + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo(); + + while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)]) + { + long timeDelta = timeDays / DaysPerLYear; + long delta = timeDelta; + + if (delta == 0) + { + delta = timeDays < 0 ? -1 : 1; + } + + long newYear = year; + + if (IncrementOverflow64(ref newYear, delta)) + { + return ResultCode.OutOfRange; + } + + long leapDays = GetLeapDays(newYear - 1) - GetLeapDays(year - 1); + timeDays -= (newYear - year) * DaysPerNYear; + timeDays -= leapDays; + year = newYear; + } + + long dayOfYear = timeDays; + remainingSeconds += gmtOffset; + while (remainingSeconds < 0) + { + remainingSeconds += SecondsPerDay; + dayOfYear -= 1; + } + + while (remainingSeconds >= SecondsPerDay) + { + remainingSeconds -= SecondsPerDay; + dayOfYear += 1; + } + + while (dayOfYear < 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.OutOfRange; + } + + dayOfYear += YearLengths[IsLeap((int)year)]; + } + + while (dayOfYear >= YearLengths[IsLeap((int)year)]) + { + dayOfYear -= YearLengths[IsLeap((int)year)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.OutOfRange; + } + } + + calendarTime.Year = year; + calendarAdditionalInfo.DayOfYear = (uint)dayOfYear; + + long dayOfWeek = (EpochWeekDay + ((year - EpochYear) % DaysPerWekk) * (DaysPerNYear % DaysPerWekk) + GetLeapDays(year - 1) - GetLeapDays(EpochYear - 1) + dayOfYear) % DaysPerWekk; + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWekk; + } + + calendarAdditionalInfo.DayOfWeek = (uint)dayOfWeek; + + calendarTime.Hour = (sbyte)((remainingSeconds / SecondsPerHour) % SecondsPerHour); + remainingSeconds %= SecondsPerHour; + + calendarTime.Minute = (sbyte)(remainingSeconds / SecondsPerMinute); + calendarTime.Second = (sbyte)(remainingSeconds % SecondsPerMinute); + + int[] ip = MonthsLengths[IsLeap((int)year)]; + + for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month) + { + dayOfYear -= ip[calendarTime.Month]; + } + + calendarTime.Day = (sbyte)(dayOfYear + 1); + + calendarAdditionalInfo.IsDaySavingTime = false; + calendarAdditionalInfo.GmtOffset = gmtOffset; + + return 0; + } + + private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo(); + + ResultCode result; + + if ((rules.GoAhead && time < rules.Ats[0]) || (rules.GoBack && time > rules.Ats[rules.TimeCount - 1])) + { + long newTime = time; + + long seconds; + long years; + + if (time < rules.Ats[0]) + { + seconds = rules.Ats[0] - time; + } + else + { + seconds = time - rules.Ats[rules.TimeCount - 1]; + } + + seconds -= 1; + + years = (seconds / SecondsPerRepeat + 1) * YearsPerRepeat; + seconds = years * AverageSecondsPerYear; + + if (time < rules.Ats[0]) + { + newTime += seconds; + } + else + { + newTime -= seconds; + } + + if (newTime < rules.Ats[0] && newTime > rules.Ats[rules.TimeCount - 1]) + { + return ResultCode.TimeNotFound; + } + + result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo); + if (result != 0) + { + return result; + } + + if (time < rules.Ats[0]) + { + calendarTime.Year -= years; + } + else + { + calendarTime.Year += years; + } + + return ResultCode.Success; + } + + int ttiIndex; + + if (rules.TimeCount == 0 || time < rules.Ats[0]) + { + ttiIndex = rules.DefaultType; + } + else + { + int low = 1; + int high = rules.TimeCount; + + while (low < high) + { + int mid = (low + high) >> 1; + + if (time < rules.Ats[mid]) + { + high = mid; + } + else + { + low = mid + 1; + } + } + + ttiIndex = rules.Types[low - 1]; + } + + result = CreateCalendarTime(time, rules.Ttis[ttiIndex].GmtOffset, out calendarTime, out calendarAdditionalInfo); + + if (result == 0) + { + calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime; + + ReadOnlySpan<byte> timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..]; + + int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8); + + timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan()); + } + + return result; + } + + private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime) + { + posixTime = 0; + + int hour = calendarTime.Hour; + int minute = calendarTime.Minute; + + if (NormalizeOverflow32(ref hour, ref minute, MinutesPerHour)) + { + return ResultCode.Overflow; + } + + calendarTime.Minute = (sbyte)minute; + + int day = calendarTime.Day; + if (NormalizeOverflow32(ref day, ref hour, HoursPerDays)) + { + return ResultCode.Overflow; + } + + calendarTime.Day = (sbyte)day; + calendarTime.Hour = (sbyte)hour; + + long year = calendarTime.Year; + long month = calendarTime.Month; + + if (NormalizeOverflow64(ref year, ref month, MonthsPerYear)) + { + return ResultCode.Overflow; + } + + calendarTime.Month = (sbyte)month; + + if (IncrementOverflow64(ref year, YearBase)) + { + return ResultCode.Overflow; + } + + while (day <= 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.Overflow; + } + + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day += YearLengths[IsLeap((int)li)]; + } + + while (day > DaysPerLYear) + { + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day -= YearLengths[IsLeap((int)li)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + + while (true) + { + int i = MonthsLengths[IsLeap((int)year)][calendarTime.Month]; + + if (day <= i) + { + break; + } + + day -= i; + calendarTime.Month += 1; + + if (calendarTime.Month >= MonthsPerYear) + { + calendarTime.Month = 0; + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + } + + calendarTime.Day = (sbyte)day; + + if (IncrementOverflow64(ref year, -YearBase)) + { + return ResultCode.Overflow; + } + + calendarTime.Year = year; + + int savedSeconds; + + if (calendarTime.Second >= 0 && calendarTime.Second < SecondsPerMinute) + { + savedSeconds = 0; + } + else if (year + YearBase < EpochYear) + { + int second = calendarTime.Second; + if (IncrementOverflow32(ref second, 1 - SecondsPerMinute)) + { + return ResultCode.Overflow; + } + + savedSeconds = second; + calendarTime.Second = 1 - SecondsPerMinute; + } + else + { + savedSeconds = calendarTime.Second; + calendarTime.Second = 0; + } + + long low = long.MinValue; + long high = long.MaxValue; + + while (true) + { + long pivot = low / 2 + high / 2; + + if (pivot < low) + { + pivot = low; + } + else if (pivot > high) + { + pivot = high; + } + + int direction; + + ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _); + if (result != 0) + { + if (pivot > 0) + { + direction = 1; + } + else + { + direction = -1; + } + } + else + { + direction = candidateCalendarTime.CompareTo(calendarTime); + } + + if (direction == 0) + { + long timeResult = pivot + savedSeconds; + + if ((timeResult < pivot) != (savedSeconds < 0)) + { + return ResultCode.Overflow; + } + + posixTime = timeResult; + break; + } + else + { + if (pivot == low) + { + if (pivot == long.MaxValue) + { + return ResultCode.TimeNotFound; + } + + pivot += 1; + low += 1; + } + else if (pivot == high) + { + if (pivot == long.MinValue) + { + return ResultCode.TimeNotFound; + } + + pivot -= 1; + high -= 1; + } + + if (low > high) + { + return ResultCode.TimeNotFound; + } + + if (direction > 0) + { + high = pivot; + } + else + { + low = pivot; + } + } + } + + return ResultCode.Success; + } + + internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo); + + calendar = new CalendarInfo() + { + Time = new CalendarTime() + { + Year = (short)calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month + 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second + }, + AdditionalInfo = calendarAdditionalInfo + }; + + return result; + } + + internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal() + { + Year = calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month - 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second + }; + + return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs new file mode 100644 index 00000000..9367024e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs @@ -0,0 +1,304 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using TimeZoneRuleBox = Ryujinx.Common.Memory.Box<Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule>; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + public class TimeZoneContentManager + { + private const long TimeZoneBinaryTitleId = 0x010000000000080E; + + private readonly string TimeZoneSystemTitleMissingErrorMessage = "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)"; + + private VirtualFileSystem _virtualFileSystem; + private IntegrityCheckLevel _fsIntegrityCheckLevel; + private ContentManager _contentManager; + + public string[] LocationNameCache { get; private set; } + + internal TimeZoneManager Manager { get; private set; } + + public TimeZoneContentManager() + { + Manager = new TimeZoneManager(); + } + + public void InitializeInstance(VirtualFileSystem virtualFileSystem, ContentManager contentManager, IntegrityCheckLevel fsIntegrityCheckLevel) + { + _virtualFileSystem = virtualFileSystem; + _contentManager = contentManager; + _fsIntegrityCheckLevel = fsIntegrityCheckLevel; + + InitializeLocationNameCache(); + } + + public string SanityCheckDeviceLocationName(string locationName) + { + if (IsLocationNameValid(locationName)) + { + return locationName; + } + + Logger.Warning?.Print(LogClass.ServiceTime, $"Invalid device TimeZone {locationName}, switching back to UTC"); + + return "UTC"; + } + + internal void Initialize(TimeManager timeManager, Switch device) + { + InitializeInstance(device.FileSystem, device.System.ContentManager, device.System.FsIntegrityCheckLevel); + + ITickSource tickSource = device.System.TickSource; + + SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(tickSource); + + string deviceLocationName = SanityCheckDeviceLocationName(device.Configuration.TimeZone); + + ResultCode result = GetTimeZoneBinary(deviceLocationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + // TODO: Read TimeZoneVersion from sysarchive. + timeManager.SetupTimeZoneManager(deviceLocationName, timeZoneUpdatedTimePoint, (uint)LocationNameCache.Length, new UInt128(), timeZoneBinaryStream); + + ncaFile.Dispose(); + } + else + { + // In the case the user don't have the timezone system archive, we just mark the manager as initialized. + Manager.MarkInitialized(); + } + } + + private void InitializeLocationNameCache() + { + if (HasTimeZoneBinaryTitle()) + { + using (IStorage ncaFileStream = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open)) + { + Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var binaryListFile = new UniqueRef<IFile>(); + + romfs.OpenFile(ref binaryListFile.Ref, "/binaryList.txt".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + StreamReader reader = new StreamReader(binaryListFile.Get.AsStream()); + + List<string> locationNameList = new List<string>(); + + string locationName; + while ((locationName = reader.ReadLine()) != null) + { + locationNameList.Add(locationName); + } + + LocationNameCache = locationNameList.ToArray(); + } + } + else + { + LocationNameCache = new string[] { "UTC" }; + + Logger.Error?.Print(LogClass.ServiceTime, TimeZoneSystemTitleMissingErrorMessage); + } + } + + public IEnumerable<(int Offset, string Location, string Abbr)> ParseTzOffsets() + { + var tzBinaryContentPath = GetTimeZoneBinaryTitleContentPath(); + + if (string.IsNullOrEmpty(tzBinaryContentPath)) + { + return new[] { (0, "UTC", "UTC") }; + } + + List<(int Offset, string Location, string Abbr)> outList = new List<(int Offset, string Location, string Abbr)>(); + var now = DateTimeOffset.Now.ToUnixTimeSeconds(); + using (IStorage ncaStorage = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), FileAccess.Read, FileMode.Open)) + using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel)) + { + foreach (string locName in LocationNameCache) + { + if (locName.StartsWith("Etc")) + { + continue; + } + + using var tzif = new UniqueRef<IFile>(); + + if (romfs.OpenFile(ref tzif.Ref, $"/zoneinfo/{locName}".ToU8Span(), OpenMode.Read).IsFailure()) + { + Logger.Error?.Print(LogClass.ServiceTime, $"Error opening /zoneinfo/{locName}"); + continue; + } + + TimeZoneRuleBox tzRuleBox = new TimeZoneRuleBox(); + ref TimeZoneRule tzRule = ref tzRuleBox.Data; + + TimeZone.ParseTimeZoneBinary(ref tzRule, tzif.Get.AsStream()); + + + TimeTypeInfo ttInfo; + if (tzRule.TimeCount > 0) // Find the current transition period + { + int fin = 0; + for (int i = 0; i < tzRule.TimeCount; ++i) + { + if (tzRule.Ats[i] <= now) + { + fin = i; + } + } + ttInfo = tzRule.Ttis[tzRule.Types[fin]]; + } + else if (tzRule.TypeCount >= 1) // Otherwise, use the first offset in TTInfo + { + ttInfo = tzRule.Ttis[0]; + } + else + { + Logger.Error?.Print(LogClass.ServiceTime, $"Couldn't find UTC offset for zone {locName}"); + continue; + } + + var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..]; + int abbrEnd = abbrStart.IndexOf((byte)0); + + outList.Add((ttInfo.GmtOffset, locName, Encoding.UTF8.GetString(abbrStart[..abbrEnd]))); + } + } + + outList.Sort(); + + return outList; + } + + private bool IsLocationNameValid(string locationName) + { + foreach (string cachedLocationName in LocationNameCache) + { + if (cachedLocationName.Equals(locationName)) + { + return true; + } + } + + return false; + } + + public ResultCode SetDeviceLocationName(string locationName) + { + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + + public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength) + { + List<string> locationNameList = new List<string>(); + + for (int i = 0; i < LocationNameCache.Length && i < maxLength; i++) + { + if (i < index) + { + continue; + } + + string locationName = LocationNameCache[i]; + + // If the location name is too long, error out. + if (locationName.Length > 0x24) + { + outLocationNameArray = Array.Empty<string>(); + + return ResultCode.LocationNameTooLong; + } + + locationNameList.Add(locationName); + } + + outLocationNameArray = locationNameList.ToArray(); + + return ResultCode.Success; + } + + public string GetTimeZoneBinaryTitleContentPath() + { + return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + } + + public bool HasTimeZoneBinaryTitle() + { + return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath()); + } + + internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile) + { + timeZoneBinaryStream = null; + ncaFile = null; + + if (!HasTimeZoneBinaryTitle() || !IsLocationNameValid(locationName)) + { + return ResultCode.TimeZoneNotFound; + } + + ncaFile = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var timeZoneBinaryFile = new UniqueRef<IFile>(); + + Result result = romfs.OpenFile(ref timeZoneBinaryFile.Ref, $"/zoneinfo/{locationName}".ToU8Span(), OpenMode.Read); + + timeZoneBinaryStream = timeZoneBinaryFile.Release().AsStream(); + + return (ResultCode)result.Value; + } + + internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName) + { + rules = default; + + if (!HasTimeZoneBinaryTitle()) + { + throw new InvalidSystemResourceException(TimeZoneSystemTitleMissingErrorMessage); + } + + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs new file mode 100644 index 00000000..ef4b7b39 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs @@ -0,0 +1,261 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + class TimeZoneManager + { + private bool _isInitialized; + private Box<TimeZoneRule> _myRules; + private string _deviceLocationName; + private UInt128 _timeZoneRuleVersion; + private uint _totalLocationNameCount; + private SteadyClockTimePoint _timeZoneUpdateTimePoint; + private object _lock; + + public TimeZoneManager() + { + _isInitialized = false; + _deviceLocationName = "UTC"; + _timeZoneRuleVersion = new UInt128(); + _lock = new object(); + _myRules = new Box<TimeZoneRule>(); + + _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom(); + } + + public bool IsInitialized() + { + bool res; + + lock (_lock) + { + res = _isInitialized; + } + + return res; + } + + public void MarkInitialized() + { + lock (_lock) + { + _isInitialized = true; + } + } + + public ResultCode GetDeviceLocationName(out string deviceLocationName) + { + ResultCode result = ResultCode.UninitializedClock; + + deviceLocationName = null; + + lock (_lock) + { + if (_isInitialized) + { + deviceLocationName = _deviceLocationName; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.TimeZoneConversionFailed; + + lock (_lock) + { + Box<TimeZoneRule> rules = new Box<TimeZoneRule>(); + + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref rules.Data, timeZoneBinaryStream); + + if (timeZoneConversionSuccess) + { + _deviceLocationName = locationName; + _myRules = rules; + result = ResultCode.Success; + } + } + + return result; + } + + public void SetTotalLocationNameCount(uint totalLocationNameCount) + { + lock (_lock) + { + _totalLocationNameCount = totalLocationNameCount; + } + } + + public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount) + { + ResultCode result = ResultCode.UninitializedClock; + + totalLocationNameCount = 0; + + lock (_lock) + { + if (_isInitialized) + { + totalLocationNameCount = _totalLocationNameCount; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false) + { + ResultCode result = ResultCode.UninitializedClock; + + lock (_lock) + { + if (_isInitialized || bypassUninitialized) + { + _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint; + result = ResultCode.Success; + } + else + { + timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.Success; + + lock (_lock) + { + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream); + + if (!timeZoneConversionSuccess) + { + result = ResultCode.TimeZoneConversionFailed; + } + } + + return result; + } + + public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion) + { + lock (_lock) + { + _timeZoneRuleVersion = timeZoneRuleVersion; + } + } + + public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneRuleVersion = _timeZoneRuleVersion; + result = ResultCode.Success; + } + else + { + timeZoneRuleVersion = new UInt128(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToCalendarTime(in _myRules.Data, time, out calendar); + } + else + { + calendar = new CalendarInfo(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToCalendarTime(in rules, time, out calendar); + } + + return result; + } + + public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime); + } + else + { + posixTime = 0; + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs new file mode 100644 index 00000000..a84a2785 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x18, CharSet = CharSet.Ansi)] + struct CalendarAdditionalInfo + { + public uint DayOfWeek; + public uint DayOfYear; + + public Array8<byte> TimezoneName; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + public Array3<byte> Padding; + + public int GmtOffset; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs new file mode 100644 index 00000000..68e6245b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x20, CharSet = CharSet.Ansi)] + struct CalendarInfo + { + public CalendarTime Time; + public CalendarAdditionalInfo AdditionalInfo; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs new file mode 100644 index 00000000..d594223d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x8)] + struct CalendarTime + { + public short Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs new file mode 100644 index 00000000..b8b3d917 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)] + public struct TimeTypeInfo + { + public const int Size = 0x10; + + public int GmtOffset; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + public Array3<byte> Padding1; + + public int AbbreviationListIndex; + + [MarshalAs(UnmanagedType.I1)] + public bool IsStandardTimeDaylight; + + [MarshalAs(UnmanagedType.I1)] + public bool IsGMT; + + public ushort Padding2; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs new file mode 100644 index 00000000..67237f3d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs @@ -0,0 +1,56 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)] + public struct TimeZoneRule + { + public const int TzMaxTypes = 128; + public const int TzMaxChars = 50; + public const int TzMaxLeaps = 50; + public const int TzMaxTimes = 1000; + public const int TzNameMax = 255; + public const int TzCharsArraySize = 2 * (TzNameMax + 1); + + public int TimeCount; + public int TypeCount; + public int CharCount; + + [MarshalAs(UnmanagedType.I1)] + public bool GoBack; + + [MarshalAs(UnmanagedType.I1)] + public bool GoAhead; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)] + private struct AtsStorageStruct { } + + private AtsStorageStruct _ats; + + public Span<long> Ats => SpanHelpers.AsSpan<AtsStorageStruct, long>(ref _ats); + + [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)] + private struct TypesStorageStruct { } + + private TypesStorageStruct _types; + + public Span<byte> Types => SpanHelpers.AsByteSpan(ref _types); + + [StructLayout(LayoutKind.Sequential, Size = TimeTypeInfo.Size * TzMaxTypes)] + private struct TimeTypeInfoStorageStruct { } + + private TimeTypeInfoStorageStruct _ttis; + + public Span<TimeTypeInfo> Ttis => SpanHelpers.AsSpan<TimeTypeInfoStorageStruct, TimeTypeInfo>(ref _ttis); + + [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzCharsArraySize)] + private struct CharsStorageStruct { } + + private CharsStorageStruct _chars; + public Span<byte> Chars => SpanHelpers.AsByteSpan(ref _chars); + + public int DefaultType; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs new file mode 100644 index 00000000..022c34a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)] + struct TzifHeader + { + public Array4<byte> Magic; + public byte Version; + private Array15<byte> _reserved; + public int TtisGMTCount; + public int TtisSTDCount; + public int LeapCount; + public int TimeCount; + public int TypeCount; + public int CharCount; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs b/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs new file mode 100644 index 00000000..38d37055 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SteadyClockContext + { + public ulong InternalOffset; + public UInt128 ClockSourceId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs b/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs new file mode 100644 index 00000000..3fcd3a14 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Flags] + enum TimePermissions + { + LocalSystemClockWritableMask = 0x1, + UserSystemClockWritableMask = 0x2, + NetworkSystemClockWritableMask = 0x4, + TimeZoneWritableMask = 0x8, + SteadyClockWritableMask = 0x10, + BypassUninitialized = 0x20, + + User = 0, + Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask, + System = NetworkSystemClockWritableMask, + SystemUpdate = BypassUninitialized, + Repair = SteadyClockWritableMask, + Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs new file mode 100644 index 00000000..56b12af0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:hs")] + [Service("usb:hs:a")] // 7.0.0+ + class IClientRootSession : IpcService + { + public IClientRootSession(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs new file mode 100644 index 00000000..4dbb6fc1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:ds")] + class IDsService : IpcService + { + public IDsService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs new file mode 100644 index 00000000..cecdbc31 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pd:c")] + class IPdCradleManager : IpcService + { + public IPdCradleManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs new file mode 100644 index 00000000..1fb574d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pd")] + class IPdManager : IpcService + { + public IPdManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs new file mode 100644 index 00000000..38beee07 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:pm")] + class IPmService : IpcService + { + public IPmService(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs new file mode 100644 index 00000000..0981e4ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:qdb")] // 7.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs new file mode 100644 index 00000000..563696bb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Usb +{ + [Service("usb:obsv")] // 8.0.0+ + class IUnknown2 : IpcService + { + public IUnknown2(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs new file mode 100644 index 00000000..526cecf8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:u")] + class IApplicationRootService : IpcService + { + public IApplicationRootService(ServiceCtx context) : base(context.Device.System.ViServer) { } + + [CommandCmif(0)] + // GetDisplayService(u32) -> object<nn::visrv::sf::IApplicationDisplayService> + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.Application) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs new file mode 100644 index 00000000..d564dabe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:m")] + class IManagerRootService : IpcService + { + // vi:u/m/s aren't on 3 separate threads but we can't put them together with the current ServerBase + public IManagerRootService(ServiceCtx context) : base(context.Device.System.ViServerM) { } + + [CommandCmif(2)] + // GetDisplayService(u32) -> object<nn::visrv::sf::IApplicationDisplayService> + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.Manager) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs new file mode 100644 index 00000000..0dfd84f5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:s")] + class ISystemRootService : IpcService + { + // vi:u/m/s aren't on 3 separate threads but we can't put them together with the current ServerBase + public ISystemRootService(ServiceCtx context) : base(context.Device.System.ViServerS) { } + + [CommandCmif(1)] + // GetDisplayService(u32) -> object<nn::visrv::sf::IApplicationDisplayService> + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs new file mode 100644 index 00000000..c64339c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Vi +{ + enum ResultCode + { + ModuleId = 114, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArguments = (1 << ErrorCodeShift) | ModuleId, + InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId, + PermissionDenied = (5 << ErrorCodeShift) | ModuleId, + InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId, + InvalidValue = (7 << ErrorCodeShift) | ModuleId, + AlreadyOpened = (9 << ErrorCodeShift) | ModuleId + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs new file mode 100644 index 00000000..1fa99e65 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + static class AndroidSurfaceComposerClient + { + // NOTE: This is android::SurfaceComposerClient::getDisplayInfo. + public static (ulong, ulong) GetDisplayInfo(ServiceCtx context, ulong displayId = 0) + { + // TODO: This need to be REd, it should returns the driver resolution and more. + if (context.Device.System.State.DockedMode) + { + return (1920, 1080); + } + else + { + return (1280, 720); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs new file mode 100644 index 00000000..6093381c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs @@ -0,0 +1,80 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class IManagerDisplayService : IpcService + { + private IApplicationDisplayService _applicationDisplayService; + + public IManagerDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [CommandCmif(1102)] + // GetDisplayResolution(u64 display_id) -> (u64 width, u64 height) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context, displayId); + + context.ResponseData.Write(width); + context.ResponseData.Write(height); + + return ResultCode.Success; + } + + [CommandCmif(2010)] + // CreateManagedLayer(u32, u64, nn::applet::AppletResourceUserId) -> u64 + public ResultCode CreateManagedLayer(ServiceCtx context) + { + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId); + + context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + context.ResponseData.Write(layerId); + + return ResultCode.Success; + } + + [CommandCmif(2011)] + // DestroyManagedLayer(u64) + public ResultCode DestroyManagedLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.DestroyManagedLayer(layerId); + } + + [CommandCmif(2012)] // 7.0.0+ + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer<bytes, 6>) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + return _applicationDisplayService.CreateStrayLayer(context); + } + + [CommandCmif(6000)] + // AddToLayerStack(u32, u64) + public ResultCode AddToLayerStack(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(6002)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs new file mode 100644 index 00000000..a24aa079 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class ISystemDisplayService : IpcService + { + private IApplicationDisplayService _applicationDisplayService; + + public ISystemDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [CommandCmif(2205)] + // SetLayerZ(u64, u64) + public ResultCode SetLayerZ(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(2207)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(2312)] // 1.0.0-6.2.0 + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer<bytes, 6>) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return _applicationDisplayService.CreateStrayLayer(context); + } + + [CommandCmif(3200)] + // GetDisplayMode(u64) -> nn::vi::DisplayModeInfo + public ResultCode GetDisplayMode(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context, displayId); + + context.ResponseData.Write((uint)width); + context.ResponseData.Write((uint)height); + context.ResponseData.Write(60.0f); + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs new file mode 100644 index 00000000..cf459cb2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum DestinationScalingMode + { + Freeze, + ScaleToWindow, + ScaleAndCrop, + None, + PreserveAspectRatio + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs new file mode 100644 index 00000000..d46206d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x60)] + struct DisplayInfo + { + public Array64<byte> Name; + public bool LayerLimitEnabled; + public Array7<byte> Padding; + public ulong LayerLimitMax; + public ulong Width; + public ulong Height; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs new file mode 100644 index 00000000..ac8c3e02 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum SourceScalingMode + { + None, + Freeze, + ScaleToWindow, + ScaleAndCrop, + PreserveAspectRatio + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs new file mode 100644 index 00000000..52ed5222 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -0,0 +1,487 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types; +using Ryujinx.HLE.HOS.Services.Vi.Types; +using Ryujinx.HLE.Ui; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService +{ + class IApplicationDisplayService : IpcService + { + private readonly ViServiceType _serviceType; + + private class DisplayState + { + public int RetrievedEventsCount; + } + + private readonly List<DisplayInfo> _displayInfo; + private readonly Dictionary<ulong, DisplayState> _openDisplays; + + private int _vsyncEventHandle; + + public IApplicationDisplayService(ViServiceType serviceType) + { + _serviceType = serviceType; + _displayInfo = new List<DisplayInfo>(); + _openDisplays = new Dictionary<ulong, DisplayState>(); + + void AddDisplayInfo(string name, bool layerLimitEnabled, ulong layerLimitMax, ulong width, ulong height) + { + DisplayInfo displayInfo = new DisplayInfo() + { + Name = new Array64<byte>(), + LayerLimitEnabled = layerLimitEnabled, + Padding = new Array7<byte>(), + LayerLimitMax = layerLimitMax, + Width = width, + Height = height + }; + + Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(displayInfo.Name.AsSpan()); + + _displayInfo.Add(displayInfo); + } + + AddDisplayInfo("Default", true, 1, 1920, 1080); + AddDisplayInfo("External", true, 1, 1920, 1080); + AddDisplayInfo("Edid", true, 1, 0, 0); + AddDisplayInfo("Internal", true, 1, 1920, 1080); + AddDisplayInfo("Null", false, 0, 1920, 1080); + } + + [CommandCmif(100)] + // GetRelayService() -> object<nns::hosbinder::IHOSBinderDriver> + public ResultCode GetRelayService(ServiceCtx context) + { + // FIXME: Should be _serviceType != ViServiceType.Application but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetSystemDisplayService() -> object<nn::visrv::sf::ISystemDisplayService> + public ResultCode GetSystemDisplayService(ServiceCtx context) + { + // FIXME: Should be _serviceType == ViServiceType.System but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new ISystemDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // GetManagerDisplayService() -> object<nn::visrv::sf::IManagerDisplayService> + public ResultCode GetManagerDisplayService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IManagerDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(103)] // 2.0.0+ + // GetIndirectDisplayTransactionService() -> object<nns::hosbinder::IHOSBinderDriver> + public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // ListDisplays() -> (u64 count, buffer<nn::vi::DisplayInfo, 6>) + public ResultCode ListDisplays(ServiceCtx context) + { + ulong displayInfoBuffer = context.Request.ReceiveBuff[0].Position; + + // TODO: Determine when more than one display is needed. + ulong displayCount = 1; + + for (int i = 0; i < (int)displayCount; i++) + { + context.Memory.Write(displayInfoBuffer + (ulong)(i * Unsafe.SizeOf<DisplayInfo>()), _displayInfo[i]); + } + + context.ResponseData.Write(displayCount); + + return ResultCode.Success; + } + + [CommandCmif(1010)] + // OpenDisplay(nn::vi::DisplayName) -> u64 display_id + public ResultCode OpenDisplay(ServiceCtx context) + { + string name = ""; + + for (int index = 0; index < 8 && context.RequestData.BaseStream.Position < context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + name += (char)chr; + } + } + + return OpenDisplayImpl(context, name); + } + + [CommandCmif(1011)] + // OpenDefaultDisplay() -> u64 display_id + public ResultCode OpenDefaultDisplay(ServiceCtx context) + { + return OpenDisplayImpl(context, "Default"); + } + + private ResultCode OpenDisplayImpl(ServiceCtx context, string name) + { + if (name == "") + { + return ResultCode.InvalidValue; + } + + int displayId = _displayInfo.FindIndex(display => Encoding.ASCII.GetString(display.Name.AsSpan()).Trim('\0') == name); + + if (displayId == -1) + { + return ResultCode.InvalidValue; + } + + if (!_openDisplays.TryAdd((ulong)displayId, new DisplayState())) + { + return ResultCode.AlreadyOpened; + } + + context.ResponseData.Write((ulong)displayId); + + return ResultCode.Success; + } + + [CommandCmif(1020)] + // CloseDisplay(u64 display_id) + public ResultCode CloseDisplay(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.Remove(displayId)) + { + return ResultCode.InvalidValue; + } + + return ResultCode.Success; + } + + [CommandCmif(1101)] + // SetDisplayEnabled(u32 enabled_bool, u64 display_id) + public ResultCode SetDisplayEnabled(ServiceCtx context) + { + // NOTE: Stubbed in original service. + return ResultCode.Success; + } + + [CommandCmif(1102)] + // GetDisplayResolution(u64 display_id) -> (u64 width, u64 height) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + // NOTE: Not used in original service. + // ulong displayId = context.RequestData.ReadUInt64(); + + // NOTE: Returns ResultCode.InvalidArguments if width and height pointer are null, doesn't occur in our case. + + // NOTE: Values are hardcoded in original service. + context.ResponseData.Write(1280UL); // Width + context.ResponseData.Write(720UL); // Height + + return ResultCode.Success; + } + + [CommandCmif(2020)] + // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer<bytes, 6>) + public ResultCode OpenLayer(ServiceCtx context) + { + // TODO: support multi display. + byte[] displayName = context.RequestData.ReadBytes(0x40); + + long layerId = context.RequestData.ReadInt64(); + long userId = context.RequestData.ReadInt64(); + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + ResultCode result = context.Device.System.SurfaceFlinger.OpenLayer(context.Request.HandleDesc.PId, layerId, out IBinder producer); + + if (result != ResultCode.Success) + { + return result; + } + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + Parcel parcel = new Parcel(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan<byte> parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2021)] + // CloseLayer(u64) + public ResultCode CloseLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.CloseLayer(layerId); + } + + [CommandCmif(2030)] + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer<bytes, 6>) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); + + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + // TODO: support multi display. + IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray); + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + Parcel parcel = new Parcel(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan<byte> parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write(layerId); + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2031)] + // DestroyStrayLayer(u64) + public ResultCode DestroyStrayLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.DestroyStrayLayer(layerId); + } + + [CommandCmif(2101)] + // SetLayerScalingMode(u32, u64) + public ResultCode SetLayerScalingMode(ServiceCtx context) + { + /* + uint sourceScalingMode = context.RequestData.ReadUInt32(); + ulong layerId = context.RequestData.ReadUInt64(); + */ + // NOTE: Original service converts SourceScalingMode to DestinationScalingMode but does nothing with the converted value. + + return ResultCode.Success; + } + + [CommandCmif(2102)] // 5.0.0+ + // ConvertScalingMode(u32 source_scaling_mode) -> u64 destination_scaling_mode + public ResultCode ConvertScalingMode(ServiceCtx context) + { + SourceScalingMode scalingMode = (SourceScalingMode)context.RequestData.ReadInt32(); + + DestinationScalingMode? convertedScalingMode = scalingMode switch + { + SourceScalingMode.None => DestinationScalingMode.None, + SourceScalingMode.Freeze => DestinationScalingMode.Freeze, + SourceScalingMode.ScaleAndCrop => DestinationScalingMode.ScaleAndCrop, + SourceScalingMode.ScaleToWindow => DestinationScalingMode.ScaleToWindow, + SourceScalingMode.PreserveAspectRatio => DestinationScalingMode.PreserveAspectRatio, + _ => null, + }; + + if (!convertedScalingMode.HasValue) + { + // Scaling mode out of the range of valid values. + return ResultCode.InvalidArguments; + } + + if (scalingMode != SourceScalingMode.ScaleToWindow && scalingMode != SourceScalingMode.PreserveAspectRatio) + { + // Invalid scaling mode specified. + return ResultCode.InvalidScalingMode; + } + + context.ResponseData.Write((ulong)convertedScalingMode); + + return ResultCode.Success; + } + + private ulong GetA8B8G8R8LayerSize(int width, int height, out int pitch, out int alignment) + { + const int defaultAlignment = 0x1000; + const ulong defaultSize = 0x20000; + + alignment = defaultAlignment; + pitch = BitUtils.AlignUp(BitUtils.DivRoundUp(width * 32, 8), 64); + + int memorySize = pitch * BitUtils.AlignUp(height, 64); + ulong requiredMemorySize = (ulong)BitUtils.AlignUp(memorySize, alignment); + + return (requiredMemorySize + defaultSize - 1) / defaultSize * defaultSize; + } + + [CommandCmif(2450)] + // GetIndirectLayerImageMap(s64 width, s64 height, u64 handle, nn::applet::AppletResourceUserId, pid) -> (s64, s64, buffer<bytes, 0x46>) + public ResultCode GetIndirectLayerImageMap(ServiceCtx context) + { + // The size of the layer buffer should be an aligned multiple of width * height + // because it was created using GetIndirectLayerImageRequiredMemoryInfo as a guide. + + long layerWidth = context.RequestData.ReadInt64(); + long layerHeight = context.RequestData.ReadInt64(); + long layerHandle = context.RequestData.ReadInt64(); + ulong layerBuffPosition = context.Request.ReceiveBuff[0].Position; + ulong layerBuffSize = context.Request.ReceiveBuff[0].Size; + + // Get the pitch of the layer that is necessary to render correctly. + ulong size = GetA8B8G8R8LayerSize((int)layerWidth, (int)layerHeight, out int pitch, out _); + + Debug.Assert(layerBuffSize == size); + + RenderingSurfaceInfo surfaceInfo = new RenderingSurfaceInfo(ColorFormat.A8B8G8R8, (uint)layerWidth, (uint)layerHeight, (uint)pitch, (uint)layerBuffSize); + + // Get the applet associated with the handle. + object appletObject = context.Device.System.AppletState.IndirectLayerHandles.GetData((int)layerHandle); + + if (appletObject == null) + { + Logger.Error?.Print(LogClass.ServiceVi, $"Indirect layer handle {layerHandle} does not match any applet"); + + return ResultCode.Success; + } + + Debug.Assert(appletObject is IApplet); + + IApplet applet = appletObject as IApplet; + + if (!applet.DrawTo(surfaceInfo, context.Memory, layerBuffPosition)) + { + Logger.Warning?.Print(LogClass.ServiceVi, $"Applet did not draw on indirect layer handle {layerHandle}"); + + return ResultCode.Success; + } + + context.ResponseData.Write(layerWidth); + context.ResponseData.Write(layerHeight); + + return ResultCode.Success; + } + + [CommandCmif(2460)] + // GetIndirectLayerImageRequiredMemoryInfo(u64 width, u64 height) -> (u64 size, u64 alignment) + public ResultCode GetIndirectLayerImageRequiredMemoryInfo(ServiceCtx context) + { + /* + // Doesn't occur in our case. + if (sizePtr == null || address_alignmentPtr == null) + { + return ResultCode.InvalidArguments; + } + */ + + int width = (int)context.RequestData.ReadUInt64(); + int height = (int)context.RequestData.ReadUInt64(); + + if (height < 0 || width < 0) + { + return ResultCode.InvalidLayerSize; + } + else + { + /* + // Doesn't occur in our case. + if (!service_initialized) + { + return ResultCode.InvalidArguments; + } + */ + + // NOTE: The official service setup a A8B8G8R8 texture with a linear layout and then query its size. + // As we don't need this texture on the emulator, we can just simplify this logic and directly + // do a linear layout size calculation. (stride * height * bytePerPixel) + ulong size = GetA8B8G8R8LayerSize(width, height, out int pitch, out int alignment); + + context.ResponseData.Write(size); + context.ResponseData.Write(alignment); + } + + return ResultCode.Success; + } + + [CommandCmif(5202)] + // GetDisplayVsyncEvent(u64) -> handle<copy> + public ResultCode GetDisplayVSyncEvent(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.TryGetValue(displayId, out DisplayState displayState)) + { + return ResultCode.InvalidValue; + } + + if (displayState.RetrievedEventsCount > 0) + { + return ResultCode.PermissionDenied; + } + + if (_vsyncEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out _vsyncEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + displayState.RetrievedEventsCount++; + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_vsyncEventHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs b/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs new file mode 100644 index 00000000..ba6f8e5f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.Types +{ + enum ViServiceType + { + Application, + Manager, + System + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs new file mode 100644 index 00000000..0416868a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:inf")] + class IInfraManager : IpcService + { + public IInfraManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs new file mode 100644 index 00000000..6c2e20a4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lga")] + class ILocalGetActionFrame : IpcService + { + public ILocalGetActionFrame(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs new file mode 100644 index 00000000..a224a192 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lg")] + class ILocalGetFrame : IpcService + { + public ILocalGetFrame(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs new file mode 100644 index 00000000..4cc2c4b2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:lcl")] + class ILocalManager : IpcService + { + public ILocalManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs new file mode 100644 index 00000000..ab5b2193 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:sg")] + class ISocketGetFrame : IpcService + { + public ISocketGetFrame(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs new file mode 100644 index 00000000..afa1bede --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:soc")] + class ISocketManager : IpcService + { + public ISocketManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs b/src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs new file mode 100644 index 00000000..dfae18e5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Wlan +{ + [Service("wlan:dtc")] // 6.0.0+ + class IUnknown1 : IpcService + { + public IUnknown1(ServiceCtx context) { } + } +}
\ No newline at end of file |
