aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs241
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs75
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs47
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs54
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs187
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs114
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs254
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs56
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpgbin0 -> 49000 bytes
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs129
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs200
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs107
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs79
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs38
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs64
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs87
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs105
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs104
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs261
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs78
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs66
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs285
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs106
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs91
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs432
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs120
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs86
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs675
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs87
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs43
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs31
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs58
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs43
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs204
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs235
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs185
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs162
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs172
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs320
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs122
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs217
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs67
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs116
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs92
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs116
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs205
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs85
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs78
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs63
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs74
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs103
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs128
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs134
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs69
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs98
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/DummyService.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs147
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs55
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs352
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs178
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs83
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs161
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs52
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs95
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs213
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs58
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs1308
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs107
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs635
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs46
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs1800
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs240
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs149
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs138
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs66
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/IpcService.cs284
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs92
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs54
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs59
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs88
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs43
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mig/IService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs328
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs501
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs266
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs425
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs329
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs911
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs197
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs69
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs141
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs120
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs254
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs2254
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs230
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs75
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs196
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs63
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs254
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs63
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs1000
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs204
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs92
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs203
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs142
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs31
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs346
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs68
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs598
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs94
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs401
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs190
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs1361
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs574
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs105
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs540
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs185
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs199
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs239
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs59
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs49
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs272
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs61
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs310
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs55
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs51
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs90
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs259
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs62
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs57
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs94
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs49
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs88
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs39
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs602
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs84
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs140
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs183
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/ServerBase.cs423
-rw-r--r--src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs247
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs348
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs4849
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs1712
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs126
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs261
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs49
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs184
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs1121
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs53
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs153
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs122
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs530
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs177
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs134
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs225
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs56
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs212
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs39
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs119
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs155
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs402
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs97
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs686
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs106
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs51
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs143
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs57
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs126
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs38
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs246
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs125
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs519
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs83
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs251
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs68
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs95
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs420
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs341
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs871
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs175
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs109
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs304
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs109
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs221
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs548
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs104
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs38
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs62
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs235
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs31
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs74
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs71
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs72
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs98
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs71
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs144
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs50
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs43
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs50
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs184
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs433
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs231
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs155
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs131
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs142
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs303
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs182
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs114
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs1703
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs304
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs261
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs56
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs80
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs59
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs487
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs8
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
new file mode 100644
index 00000000..64c4e8ec
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg
Binary files differ
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