aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS')
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/AppletManager.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs104
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs133
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs47
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs62
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs147
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs216
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/IApplet.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs58
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs93
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs298
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs56
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs31
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.pngbin0 -> 1074 bytes
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg80
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.pngbin0 -> 992 bytes
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg93
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.pngbin0 -> 842 bytes
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg108
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.pngbin0 -> 52972 bytes
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs119
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs100
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs816
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs220
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs182
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs138
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs164
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs606
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs186
-rw-r--r--src/Ryujinx.HLE/HOS/ArmProcessContext.cs80
-rw-r--r--src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs89
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs59
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs113
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs40
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs61
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs55
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs39
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs120
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs47
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs89
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs3367
-rw-r--r--src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Horizon.cs556
-rw-r--r--src/Ryujinx.HLE/HOS/IdDictionary.cs75
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs93
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs283
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs58
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs4
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs73
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs188
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs78
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs218
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs89
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs73
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs128
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs217
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs144
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs84
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs72
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs87
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs1246
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs54
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs160
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs73
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs169
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs156
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs288
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs242
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs298
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs283
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs97
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs229
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs3043
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs75
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs50
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs130
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs50
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs49
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs465
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs83
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs285
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs1196
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs328
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs77
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs61
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs46
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs3010
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs581
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs70
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs64
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs286
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs661
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs142
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs1438
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs120
-rw-r--r--src/Ryujinx.HLE/HOS/ModLoader.cs761
-rw-r--r--src/Ryujinx.HLE/HOS/ResultCode.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/ServiceCtx.cs40
-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
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs42
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs90
-rw-r--r--src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs152
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs105
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs87
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs91
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs57
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs58
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs47
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs106
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs72
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs71
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs99
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CodeType.cs110
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Comparison.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs75
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs133
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs95
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Parameter.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Pointer.cs32
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Register.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs68
-rw-r--r--src/Ryujinx.HLE/HOS/Tamper/Value.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/TamperMachine.cs186
-rw-r--r--src/Ryujinx.HLE/HOS/UserChannelPersistence.cs60
1057 files changed, 95913 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs
new file mode 100644
index 00000000..a686a832
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs
@@ -0,0 +1,37 @@
+using Ryujinx.HLE.HOS.Applets.Browser;
+using Ryujinx.HLE.HOS.Applets.Error;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ static class AppletManager
+ {
+ private static Dictionary<AppletId, Type> _appletMapping;
+
+ static AppletManager()
+ {
+ _appletMapping = new Dictionary<AppletId, Type>
+ {
+ { AppletId.Error, typeof(ErrorApplet) },
+ { AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
+ { AppletId.Controller, typeof(ControllerApplet) },
+ { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
+ { AppletId.LibAppletWeb, typeof(BrowserApplet) },
+ { AppletId.LibAppletShop, typeof(BrowserApplet) },
+ { AppletId.LibAppletOff, typeof(BrowserApplet) }
+ };
+ }
+
+ public static IApplet Create(AppletId applet, Horizon system)
+ {
+ if (_appletMapping.TryGetValue(applet, out Type appletClass))
+ {
+ return (IApplet)Activator.CreateInstance(appletClass, system);
+ }
+
+ throw new NotImplementedException($"{applet} applet is not implemented.");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs
new file mode 100644
index 00000000..fe6e6040
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ enum BootDisplayKind
+ {
+ White,
+ Offline,
+ Black,
+ Share,
+ Lobby
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs
new file mode 100644
index 00000000..952afcd5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ internal class BrowserApplet : IApplet
+ {
+ public event EventHandler AppletStateChanged;
+
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
+
+ private CommonArguments _commonArguments;
+ private List<BrowserArgument> _arguments;
+ private ShimKind _shimKind;
+
+ public BrowserApplet(Horizon system) {}
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+
+ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+ _interactiveSession = interactiveSession;
+
+ _commonArguments = IApplet.ReadStruct<CommonArguments>(_normalSession.Pop());
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm, $"WebApplet version: 0x{_commonArguments.AppletVersion:x8}");
+
+ ReadOnlySpan<byte> webArguments = _normalSession.Pop();
+
+ (_shimKind, _arguments) = BrowserArgument.ParseArguments(webArguments);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm, $"Web Arguments: {_arguments.Count}");
+
+ foreach (BrowserArgument argument in _arguments)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm, $"{argument.Type}: {argument.GetValue()}");
+ }
+
+ if ((_commonArguments.AppletVersion >= 0x80000 && _shimKind == ShimKind.Web) || (_commonArguments.AppletVersion >= 0x30000 && _shimKind == ShimKind.Share))
+ {
+ List<BrowserOutput> result = new List<BrowserOutput>();
+
+ result.Add(new BrowserOutput(BrowserOutputType.ExitReason, (uint)WebExitReason.ExitButton));
+
+ _normalSession.Push(BuildResponseNew(result));
+ }
+ else
+ {
+ WebCommonReturnValue result = new WebCommonReturnValue()
+ {
+ ExitReason = WebExitReason.ExitButton,
+ };
+
+ _normalSession.Push(BuildResponseOld(result));
+ }
+
+ AppletStateChanged?.Invoke(this, null);
+
+ return ResultCode.Success;
+ }
+
+ private byte[] BuildResponseOld(WebCommonReturnValue result)
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.WriteStruct(result);
+
+ return stream.ToArray();
+ }
+ }
+ private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.WriteStruct(new WebArgHeader
+ {
+ Count = (ushort)outputArguments.Count,
+ ShimKind = _shimKind
+ });
+
+ foreach (BrowserOutput output in outputArguments)
+ {
+ output.Write(writer);
+ }
+
+ writer.Write(new byte[0x2000 - writer.BaseStream.Position]);
+
+ return stream.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs
new file mode 100644
index 00000000..17fd4089
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs
@@ -0,0 +1,133 @@
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ class BrowserArgument
+ {
+ public WebArgTLVType Type { get; }
+ public byte[] Value { get; }
+
+ public BrowserArgument(WebArgTLVType type, byte[] value)
+ {
+ Type = type;
+ Value = value;
+ }
+
+ private static readonly Dictionary<WebArgTLVType, Type> _typeRegistry = new Dictionary<WebArgTLVType, Type>
+ {
+ { WebArgTLVType.InitialURL, typeof(string) },
+ { WebArgTLVType.CallbackUrl, typeof(string) },
+ { WebArgTLVType.CallbackableUrl, typeof(string) },
+ { WebArgTLVType.ApplicationId, typeof(ulong) },
+ { WebArgTLVType.DocumentPath, typeof(string) },
+ { WebArgTLVType.DocumentKind, typeof(DocumentKind) },
+ { WebArgTLVType.SystemDataId, typeof(ulong) },
+ { WebArgTLVType.Whitelist, typeof(string) },
+ { WebArgTLVType.NewsFlag, typeof(bool) },
+ { WebArgTLVType.UserID, typeof(UserId) },
+ { WebArgTLVType.ScreenShotEnabled, typeof(bool) },
+ { WebArgTLVType.EcClientCertEnabled, typeof(bool) },
+ { WebArgTLVType.UnknownFlag0x14, typeof(bool) },
+ { WebArgTLVType.UnknownFlag0x15, typeof(bool) },
+ { WebArgTLVType.PlayReportEnabled, typeof(bool) },
+ { WebArgTLVType.BootDisplayKind, typeof(BootDisplayKind) },
+ { WebArgTLVType.FooterEnabled, typeof(bool) },
+ { WebArgTLVType.PointerEnabled, typeof(bool) },
+ { WebArgTLVType.LeftStickMode, typeof(LeftStickMode) },
+ { WebArgTLVType.KeyRepeatFrame1, typeof(int) },
+ { WebArgTLVType.KeyRepeatFrame2, typeof(int) },
+ { WebArgTLVType.BootAsMediaPlayerInverted, typeof(bool) },
+ { WebArgTLVType.DisplayUrlKind, typeof(bool) },
+ { WebArgTLVType.BootAsMediaPlayer, typeof(bool) },
+ { WebArgTLVType.ShopJumpEnabled, typeof(bool) },
+ { WebArgTLVType.MediaAutoPlayEnabled, typeof(bool) },
+ { WebArgTLVType.LobbyParameter, typeof(string) },
+ { WebArgTLVType.JsExtensionEnabled, typeof(bool) },
+ { WebArgTLVType.AdditionalCommentText, typeof(string) },
+ { WebArgTLVType.TouchEnabledOnContents, typeof(bool) },
+ { WebArgTLVType.UserAgentAdditionalString, typeof(string) },
+ { WebArgTLVType.MediaPlayerAutoCloseEnabled, typeof(bool) },
+ { WebArgTLVType.PageCacheEnabled, typeof(bool) },
+ { WebArgTLVType.WebAudioEnabled, typeof(bool) },
+ { WebArgTLVType.PageFadeEnabled, typeof(bool) },
+ { WebArgTLVType.BootLoadingIconEnabled, typeof(bool) },
+ { WebArgTLVType.PageScrollIndicatorEnabled, typeof(bool) },
+ { WebArgTLVType.MediaPlayerSpeedControlEnabled, typeof(bool) },
+ { WebArgTLVType.OverrideWebAudioVolume, typeof(float) },
+ { WebArgTLVType.OverrideMediaAudioVolume, typeof(float) },
+ { WebArgTLVType.MediaPlayerUiEnabled, typeof(bool) },
+ };
+
+ public static (ShimKind, List<BrowserArgument>) ParseArguments(ReadOnlySpan<byte> data)
+ {
+ List<BrowserArgument> browserArguments = new List<BrowserArgument>();
+
+ WebArgHeader header = IApplet.ReadStruct<WebArgHeader>(data.Slice(0, 8));
+
+ ReadOnlySpan<byte> rawTLVs = data.Slice(8);
+
+ for (int i = 0; i < header.Count; i++)
+ {
+ WebArgTLV tlv = IApplet.ReadStruct<WebArgTLV>(rawTLVs);
+ ReadOnlySpan<byte> tlvData = rawTLVs.Slice(Unsafe.SizeOf<WebArgTLV>(), tlv.Size);
+
+ browserArguments.Add(new BrowserArgument((WebArgTLVType)tlv.Type, tlvData.ToArray()));
+
+ rawTLVs = rawTLVs.Slice(Unsafe.SizeOf<WebArgTLV>() + tlv.Size);
+ }
+
+ return (header.ShimKind, browserArguments);
+ }
+
+ public object GetValue()
+ {
+ if (_typeRegistry.TryGetValue(Type, out Type valueType))
+ {
+ if (valueType == typeof(string))
+ {
+ return Encoding.UTF8.GetString(Value);
+ }
+ else if (valueType == typeof(bool))
+ {
+ return Value[0] == 1;
+ }
+ else if (valueType == typeof(uint))
+ {
+ return BitConverter.ToUInt32(Value);
+ }
+ else if (valueType == typeof(int))
+ {
+ return BitConverter.ToInt32(Value);
+ }
+ else if (valueType == typeof(ulong))
+ {
+ return BitConverter.ToUInt64(Value);
+ }
+ else if (valueType == typeof(long))
+ {
+ return BitConverter.ToInt64(Value);
+ }
+ else if (valueType == typeof(float))
+ {
+ return BitConverter.ToSingle(Value);
+ }
+ else if (valueType == typeof(UserId))
+ {
+ return new UserId(Value);
+ }
+ else if (valueType.IsEnum)
+ {
+ return Enum.ToObject(valueType, BitConverter.ToInt32(Value));
+ }
+
+ return $"{valueType.Name} parsing not implemented";
+ }
+
+ return $"Unknown value format (raw length: {Value.Length})";
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs
new file mode 100644
index 00000000..0b368262
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs
@@ -0,0 +1,47 @@
+using Ryujinx.Common;
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ class BrowserOutput
+ {
+ public BrowserOutputType Type { get; }
+ public byte[] Value { get; }
+
+ public BrowserOutput(BrowserOutputType type, byte[] value)
+ {
+ Type = type;
+ Value = value;
+ }
+
+ public BrowserOutput(BrowserOutputType type, uint value)
+ {
+ Type = type;
+ Value = BitConverter.GetBytes(value);
+ }
+
+ public BrowserOutput(BrowserOutputType type, ulong value)
+ {
+ Type = type;
+ Value = BitConverter.GetBytes(value);
+ }
+
+ public BrowserOutput(BrowserOutputType type, bool value)
+ {
+ Type = type;
+ Value = BitConverter.GetBytes(value);
+ }
+
+ public void Write(BinaryWriter writer)
+ {
+ writer.WriteStruct(new WebArgTLV
+ {
+ Type = (ushort)Type,
+ Size = (ushort)Value.Length
+ });
+
+ writer.Write(Value);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs
new file mode 100644
index 00000000..209ae8ae
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ enum BrowserOutputType : ushort
+ {
+ ExitReason = 0x1,
+ LastUrl = 0x2,
+ LastUrlSize = 0x3,
+ SharePostResult = 0x4,
+ PostServiceName = 0x5,
+ PostServiceNameSize = 0x6,
+ PostId = 0x7,
+ MediaPlayerAutoClosedByCompletion = 0x8
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs
new file mode 100644
index 00000000..385bcdd0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ enum DocumentKind
+ {
+ OfflineHtmlPage = 1,
+ ApplicationLegalInformation,
+ SystemDataPage
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs
new file mode 100644
index 00000000..917549d2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ enum LeftStickMode
+ {
+ Pointer = 0,
+ Cursor
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs
new file mode 100644
index 00000000..ca2ef32f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ public enum ShimKind : uint
+ {
+ Shop = 1,
+ Login,
+ Offline,
+ Share,
+ Web,
+ Wifi,
+ Lobby
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs
new file mode 100644
index 00000000..c5e19f6c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ public struct WebArgHeader
+ {
+ public ushort Count;
+ public ushort Padding;
+ public ShimKind ShimKind;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs
new file mode 100644
index 00000000..f6c1e5ae
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ public struct WebArgTLV
+ {
+ public ushort Type;
+ public ushort Size;
+ public uint Padding;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs
new file mode 100644
index 00000000..bd303207
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs
@@ -0,0 +1,62 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ enum WebArgTLVType : ushort
+ {
+ InitialURL = 0x1,
+ CallbackUrl = 0x3,
+ CallbackableUrl = 0x4,
+ ApplicationId = 0x5,
+ DocumentPath = 0x6,
+ DocumentKind = 0x7,
+ SystemDataId = 0x8,
+ ShareStartPage = 0x9,
+ Whitelist = 0xA,
+ NewsFlag = 0xB,
+ UserID = 0xE,
+ AlbumEntry0 = 0xF,
+ ScreenShotEnabled = 0x10,
+ EcClientCertEnabled = 0x11,
+ PlayReportEnabled = 0x13,
+ UnknownFlag0x14 = 0x14,
+ UnknownFlag0x15 = 0x15,
+ BootDisplayKind = 0x17,
+ BackgroundKind = 0x18,
+ FooterEnabled = 0x19,
+ PointerEnabled = 0x1A,
+ LeftStickMode = 0x1B,
+ KeyRepeatFrame1 = 0x1C,
+ KeyRepeatFrame2 = 0x1D,
+ BootAsMediaPlayerInverted = 0x1E,
+ DisplayUrlKind = 0x1F,
+ BootAsMediaPlayer = 0x21,
+ ShopJumpEnabled = 0x22,
+ MediaAutoPlayEnabled = 0x23,
+ LobbyParameter = 0x24,
+ ApplicationAlbumEntry = 0x26,
+ JsExtensionEnabled = 0x27,
+ AdditionalCommentText = 0x28,
+ TouchEnabledOnContents = 0x29,
+ UserAgentAdditionalString = 0x2A,
+ AdditionalMediaData0 = 0x2B,
+ MediaPlayerAutoCloseEnabled = 0x2C,
+ PageCacheEnabled = 0x2D,
+ WebAudioEnabled = 0x2E,
+ FooterFixedKind = 0x32,
+ PageFadeEnabled = 0x33,
+ MediaCreatorApplicationRatingAge = 0x34,
+ BootLoadingIconEnabled = 0x35,
+ PageScrollIndicatorEnabled = 0x36,
+ MediaPlayerSpeedControlEnabled = 0x37,
+ AlbumEntry1 = 0x38,
+ AlbumEntry2 = 0x39,
+ AlbumEntry3 = 0x3A,
+ AdditionalMediaData1 = 0x3B,
+ AdditionalMediaData2 = 0x3C,
+ AdditionalMediaData3 = 0x3D,
+ BootFooterButton = 0x3E,
+ OverrideWebAudioVolume = 0x3F,
+ OverrideMediaAudioVolume = 0x40,
+ BootMode = 0x41,
+ MediaPlayerUiEnabled = 0x43
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs
new file mode 100644
index 00000000..9f7eae70
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ public struct WebCommonReturnValue
+ {
+ public WebExitReason ExitReason;
+ public uint Padding;
+ public ByteArray4096 LastUrl;
+ public ulong LastUrlSize;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs
new file mode 100644
index 00000000..4e44d34a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+ public enum WebExitReason : uint
+ {
+ ExitButton,
+ BackButton,
+ Requested,
+ LastUrl,
+ ErrorDialog = 7
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs b/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs
new file mode 100644
index 00000000..5da34db1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs
@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 8)]
+ struct CommonArguments
+ {
+ public uint Version;
+ public uint StructureSize;
+ public uint AppletVersion;
+ public uint ThemeColor;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PlayStartupSound;
+ public ulong SystemTicks;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs
new file mode 100644
index 00000000..5d5a26c2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs
@@ -0,0 +1,147 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Services.Hid.Types;
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ internal class ControllerApplet : IApplet
+ {
+ private Horizon _system;
+
+ private AppletSession _normalSession;
+
+ public event EventHandler AppletStateChanged;
+
+ public ControllerApplet(Horizon system)
+ {
+ _system = system;
+ }
+
+ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+
+ byte[] launchParams = _normalSession.Pop();
+ byte[] controllerSupportArgPrivate = _normalSession.Pop();
+ ControllerSupportArgPrivate privateArg = IApplet.ReadStruct<ControllerSupportArgPrivate>(controllerSupportArgPrivate);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode} " +
+ $"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}");
+
+ if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport)
+ {
+ _normalSession.Push(BuildResponse()); // Dummy response for other modes
+ AppletStateChanged?.Invoke(this, null);
+
+ return ResultCode.Success;
+ }
+
+ byte[] controllerSupportArg = _normalSession.Pop();
+
+ ControllerSupportArgHeader argHeader;
+
+ if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgV7>())
+ {
+ ControllerSupportArgV7 arg = IApplet.ReadStruct<ControllerSupportArgV7>(controllerSupportArg);
+ argHeader = arg.Header;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version 7 EnableExplainText={arg.EnableExplainText != 0}");
+ // Read enable text here?
+ }
+ else if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgVPre7>())
+ {
+ ControllerSupportArgVPre7 arg = IApplet.ReadStruct<ControllerSupportArgVPre7>(controllerSupportArg);
+ argHeader = arg.Header;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Pre-7 EnableExplainText={arg.EnableExplainText != 0}");
+ // Read enable text here?
+ }
+ else
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Unknown");
+
+ argHeader = IApplet.ReadStruct<ControllerSupportArgHeader>(controllerSupportArg); // Read just the header
+ }
+
+ int playerMin = argHeader.PlayerCountMin;
+ int playerMax = argHeader.PlayerCountMax;
+ bool singleMode = argHeader.EnableSingleMode != 0;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {playerMin} {playerMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}");
+
+ if (singleMode)
+ {
+ // Applications can set an arbitrary player range even with SingleMode, so clamp it
+ playerMin = playerMax = 1;
+ }
+
+ int configuredCount = 0;
+ PlayerIndex primaryIndex = PlayerIndex.Unknown;
+ while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex))
+ {
+ ControllerAppletUiArgs uiArgs = new ControllerAppletUiArgs
+ {
+ PlayerCountMin = playerMin,
+ PlayerCountMax = playerMax,
+ SupportedStyles = (ControllerType)privateArg.NpadStyleSet,
+ SupportedPlayers = _system.Device.Hid.Npads.GetSupportedPlayers(),
+ IsDocked = _system.State.DockedMode
+ };
+
+ if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs))
+ {
+ break;
+ }
+ }
+
+ ControllerSupportResultInfo result = new ControllerSupportResultInfo
+ {
+ PlayerCount = (sbyte)configuredCount,
+ SelectedId = (uint)GetNpadIdTypeFromIndex(primaryIndex)
+ };
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}");
+
+ _normalSession.Push(BuildResponse(result));
+ AppletStateChanged?.Invoke(this, null);
+
+ _system.ReturnFocus();
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+
+ private byte[] BuildResponse(ControllerSupportResultInfo result)
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
+
+ return stream.ToArray();
+ }
+ }
+
+ private byte[] BuildResponse()
+ {
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write((ulong)ResultCode.Success);
+
+ return stream.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
new file mode 100644
index 00000000..cc15a406
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Hid;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ public struct ControllerAppletUiArgs
+ {
+ public int PlayerCountMin;
+ public int PlayerCountMax;
+ public ControllerType SupportedStyles;
+ public IEnumerable<PlayerIndex> SupportedPlayers;
+ public bool IsDocked;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs
new file mode 100644
index 00000000..141994a8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs
@@ -0,0 +1,18 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+#pragma warning disable CS0649
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ControllerSupportArgHeader
+ {
+ public sbyte PlayerCountMin;
+ public sbyte PlayerCountMax;
+ public byte EnableTakeOverConnection;
+ public byte EnableLeftJustify;
+ public byte EnablePermitJoyDual;
+ public byte EnableSingleMode;
+ public byte EnableIdentificationColor;
+ }
+#pragma warning restore CS0649
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs
new file mode 100644
index 00000000..d4c8177e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.HLE.HOS.Applets
+{
+#pragma warning disable CS0649
+ struct ControllerSupportArgPrivate
+ {
+ public uint PrivateSize;
+ public uint ArgSize;
+ public byte Flag0;
+ public byte Flag1;
+ public ControllerSupportMode Mode;
+ public byte ControllerSupportCaller;
+ public uint NpadStyleSet;
+ public uint NpadJoyHoldType;
+ }
+#pragma warning restore CS0649
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs
new file mode 100644
index 00000000..98c413be
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+#pragma warning disable CS0649
+ // (8.0.0+ version)
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ControllerSupportArgV7
+ {
+ public ControllerSupportArgHeader Header;
+ public Array8<uint> IdentificationColor;
+ public byte EnableExplainText;
+ public ExplainTextStruct ExplainText;
+
+ [StructLayout(LayoutKind.Sequential, Size = 8 * 0x81)]
+ public struct ExplainTextStruct
+ {
+ private byte element;
+
+ public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref element, 8 * 0x81);
+ }
+ }
+#pragma warning restore CS0649
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs
new file mode 100644
index 00000000..87417e16
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+#pragma warning disable CS0649
+ // (1.0.0+ version)
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ControllerSupportArgVPre7
+ {
+ public ControllerSupportArgHeader Header;
+ public Array4<uint> IdentificationColor;
+ public byte EnableExplainText;
+ public ExplainTextStruct ExplainText;
+
+ [StructLayout(LayoutKind.Sequential, Size = 4 * 0x81)]
+ public struct ExplainTextStruct
+ {
+ private byte element;
+
+ public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref element, 4 * 0x81);
+ }
+ }
+#pragma warning restore CS0649
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs
new file mode 100644
index 00000000..9496c1dd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets
+{
+ enum ControllerSupportMode : byte
+ {
+ ShowControllerSupport = 0,
+ ShowControllerStrapGuide = 1,
+ ShowControllerFirmwareUpdate = 2
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs
new file mode 100644
index 00000000..689a54de
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+#pragma warning disable CS0649
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ControllerSupportResultInfo
+ {
+ public sbyte PlayerCount;
+ private Array3<byte> _padding;
+ public uint SelectedId;
+ public uint Result;
+ }
+#pragma warning restore CS0649
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs
new file mode 100644
index 00000000..f40d5411
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ApplicationErrorArg
+ {
+ public uint ErrorNumber;
+ public ulong LanguageCode;
+ public ByteArray2048 MessageText;
+ public ByteArray2048 DetailsText;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
new file mode 100644
index 00000000..c5c6e8e9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
@@ -0,0 +1,216 @@
+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.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.SystemState;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ internal partial class ErrorApplet : IApplet
+ {
+ private const long ErrorMessageBinaryTitleId = 0x0100000000000801;
+
+ private Horizon _horizon;
+ private AppletSession _normalSession;
+ private CommonArguments _commonArguments;
+ private ErrorCommonHeader _errorCommonHeader;
+ private byte[] _errorStorage;
+
+ public event EventHandler AppletStateChanged;
+
+ [GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")]
+ private static partial Regex CleanTextRegex();
+
+ public ErrorApplet(Horizon horizon)
+ {
+ _horizon = horizon;
+ }
+
+ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+ _commonArguments = IApplet.ReadStruct<CommonArguments>(_normalSession.Pop());
+
+ Logger.Info?.PrintMsg(LogClass.ServiceAm, $"ErrorApplet version: 0x{_commonArguments.AppletVersion:x8}");
+
+ _errorStorage = _normalSession.Pop();
+ _errorCommonHeader = IApplet.ReadStruct<ErrorCommonHeader>(_errorStorage);
+ _errorStorage = _errorStorage.Skip(Marshal.SizeOf<ErrorCommonHeader>()).ToArray();
+
+ switch (_errorCommonHeader.Type)
+ {
+ case ErrorType.ErrorCommonArg:
+ {
+ ParseErrorCommonArg();
+
+ break;
+ }
+ case ErrorType.ApplicationErrorArg:
+ {
+ ParseApplicationErrorArg();
+
+ break;
+ }
+ default: throw new NotImplementedException($"ErrorApplet type {_errorCommonHeader.Type} is not implemented.");
+ }
+
+ AppletStateChanged?.Invoke(this, null);
+
+ return ResultCode.Success;
+ }
+
+ private (uint module, uint description) HexToResultCode(uint resultCode)
+ {
+ return ((resultCode & 0x1FF) + 2000, (resultCode >> 9) & 0x3FFF);
+ }
+
+ private string SystemLanguageToLanguageKey(SystemLanguage systemLanguage)
+ {
+ return systemLanguage switch
+ {
+ SystemLanguage.Japanese => "ja",
+ SystemLanguage.AmericanEnglish => "en-US",
+ SystemLanguage.French => "fr",
+ SystemLanguage.German => "de",
+ SystemLanguage.Italian => "it",
+ SystemLanguage.Spanish => "es",
+ SystemLanguage.Chinese => "zh-Hans",
+ SystemLanguage.Korean => "ko",
+ SystemLanguage.Dutch => "nl",
+ SystemLanguage.Portuguese => "pt",
+ SystemLanguage.Russian => "ru",
+ SystemLanguage.Taiwanese => "zh-HansT",
+ SystemLanguage.BritishEnglish => "en-GB",
+ SystemLanguage.CanadianFrench => "fr-CA",
+ SystemLanguage.LatinAmericanSpanish => "es-419",
+ SystemLanguage.SimplifiedChinese => "zh-Hans",
+ SystemLanguage.TraditionalChinese => "zh-Hant",
+ SystemLanguage.BrazilianPortuguese => "pt-BR",
+ _ => "en-US"
+ };
+ }
+
+ private static string CleanText(string value)
+ {
+ return CleanTextRegex().Replace(value, "").Replace("\0", "");
+ }
+
+ private string GetMessageText(uint module, uint description, string key)
+ {
+ string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data);
+
+ using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open))
+ {
+ Nca nca = new Nca(_horizon.Device.FileSystem.KeySet, ncaFileStream);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
+ string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
+ string filePath = $"/{module}/{description:0000}/{languageCode}_{key}";
+
+ if (romfs.FileExists(filePath))
+ {
+ using var binaryFile = new UniqueRef<IFile>();
+
+ romfs.OpenFile(ref binaryFile.Ref, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ StreamReader reader = new StreamReader(binaryFile.Get.AsStream(), Encoding.Unicode);
+
+ return CleanText(reader.ReadToEnd());
+ }
+ else
+ {
+ return "";
+ }
+ }
+ }
+
+ private string[] GetButtonsText(uint module, uint description, string key)
+ {
+ string buttonsText = GetMessageText(module, description, key);
+
+ return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
+ }
+
+ private void ParseErrorCommonArg()
+ {
+ ErrorCommonArg errorCommonArg = IApplet.ReadStruct<ErrorCommonArg>(_errorStorage);
+
+ uint module = errorCommonArg.Module;
+ uint description = errorCommonArg.Description;
+
+ if (_errorCommonHeader.MessageFlag == 0)
+ {
+ (module, description) = HexToResultCode(errorCommonArg.ResultCode);
+ }
+
+ string message = GetMessageText(module, description, "DlgMsg");
+
+ if (message == "")
+ {
+ message = "An error has occured.\n\n"
+ + "Please try again later.\n\n"
+ + "If the problem persists, please refer to the Ryujinx website.\n"
+ + "www.ryujinx.org";
+ }
+
+ string[] buttons = GetButtonsText(module, description, "DlgBtn");
+
+ bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
+ if (showDetails)
+ {
+ message = GetMessageText(module, description, "FlvMsg");
+ buttons = GetButtonsText(module, description, "FlvBtn");
+
+ _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
+ }
+ }
+
+ private void ParseApplicationErrorArg()
+ {
+ ApplicationErrorArg applicationErrorArg = IApplet.ReadStruct<ApplicationErrorArg>(_errorStorage);
+
+ byte[] messageTextBuffer = new byte[0x800];
+ byte[] detailsTextBuffer = new byte[0x800];
+
+ applicationErrorArg.MessageText.AsSpan().CopyTo(messageTextBuffer);
+ applicationErrorArg.DetailsText.AsSpan().CopyTo(detailsTextBuffer);
+
+ string messageText = Encoding.ASCII.GetString(messageTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray());
+ string detailsText = Encoding.ASCII.GetString(detailsTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray());
+
+ List<string> buttons = new List<string>();
+
+ // TODO: Handle the LanguageCode to return the translated "OK" and "Details".
+
+ if (detailsText.Trim() != "")
+ {
+ buttons.Add("Details");
+ }
+
+ buttons.Add("OK");
+
+ bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
+ if (showDetails)
+ {
+ buttons.RemoveAt(0);
+
+ _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
+ }
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
new file mode 100644
index 00000000..530a2ad8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ErrorCommonArg
+ {
+ public uint Module;
+ public uint Description;
+ public uint ResultCode;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
new file mode 100644
index 00000000..b93cdd4f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ErrorCommonHeader
+ {
+ public ErrorType Type;
+ public byte JumpFlag;
+ public byte ReservedFlag1;
+ public byte ReservedFlag2;
+ public byte ReservedFlag3;
+ public byte ContextFlag;
+ public byte MessageFlag;
+ public byte ContextFlag2;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
new file mode 100644
index 00000000..f06af1d3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ enum ErrorType : byte
+ {
+ ErrorCommonArg,
+ SystemErrorArg,
+ ApplicationErrorArg,
+ ErrorEulaArg,
+ ErrorPctlArg,
+ ErrorRecordArg,
+ SystemUpdateEulaArg = 8
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs
new file mode 100644
index 00000000..224d6787
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs
@@ -0,0 +1,28 @@
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.Ui;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ interface IApplet
+ {
+ event EventHandler AppletStateChanged;
+
+ ResultCode Start(AppletSession normalSession,
+ AppletSession interactiveSession);
+
+ ResultCode GetResult();
+
+ bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
+ {
+ return false;
+ }
+
+ static T ReadStruct<T>(ReadOnlySpan<byte> data) where T : unmanaged
+ {
+ return MemoryMarshal.Cast<byte, T>(data)[0];
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
new file mode 100644
index 00000000..a8119a47
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ internal class PlayerSelectApplet : IApplet
+ {
+ private Horizon _system;
+
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
+
+ public event EventHandler AppletStateChanged;
+
+ public PlayerSelectApplet(Horizon system)
+ {
+ _system = system;
+ }
+
+ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+ _interactiveSession = interactiveSession;
+
+ // TODO(jduncanator): Parse PlayerSelectConfig from input data
+ _normalSession.Push(BuildResponse());
+
+ AppletStateChanged?.Invoke(this, null);
+
+ _system.ReturnFocus();
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+
+ private byte[] BuildResponse()
+ {
+ UserProfile currentUser = _system.AccountManager.LastOpenedUser;
+
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write((ulong)PlayerSelectResult.Success);
+
+ currentUser.UserId.Write(writer);
+
+ return stream.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs
new file mode 100644
index 00000000..682e094e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Applets
+{
+ enum PlayerSelectResult : ulong
+ {
+ Success = 0,
+ Failure = 2
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs
new file mode 100644
index 00000000..727b6d27
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies the initial position of the cursor displayed in the area.
+ /// </summary>
+ enum InitialCursorPosition : uint
+ {
+ /// <summary>
+ /// Position the cursor at the beginning of the text
+ /// </summary>
+ Start,
+
+ /// <summary>
+ /// Position the cursor at the end of the text
+ /// </summary>
+ End
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs
new file mode 100644
index 00000000..b17debfc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs
@@ -0,0 +1,48 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Possible requests to the software keyboard when running in inline mode.
+ /// </summary>
+ enum InlineKeyboardRequest : uint
+ {
+ /// <summary>
+ /// Finalize the keyboard applet.
+ /// </summary>
+ Finalize = 0x4,
+
+ /// <summary>
+ /// Set user words for text prediction.
+ /// </summary>
+ SetUserWordInfo = 0x6,
+
+ /// <summary>
+ /// Sets the CustomizeDic data. Can't be used if CustomizedDictionaries is already set.
+ /// </summary>
+ SetCustomizeDic = 0x7,
+
+ /// <summary>
+ /// Configure the keyboard applet and put it in a state where it is processing input.
+ /// </summary>
+ Calc = 0xA,
+
+ /// <summary>
+ /// Set custom dictionaries for text prediction. Can't be used if SetCustomizeDic is already set.
+ /// </summary>
+ SetCustomizedDictionaries = 0xB,
+
+ /// <summary>
+ /// Release custom dictionaries data.
+ /// </summary>
+ UnsetCustomizedDictionaries = 0xC,
+
+ /// <summary>
+ /// [8.0.0+] Request the keyboard applet to use the ChangedStringV2 response when notifying changes in text data.
+ /// </summary>
+ UseChangedStringV2 = 0xD,
+
+ /// <summary>
+ /// [8.0.0+] Request the keyboard applet to use the MovedCursorV2 response when notifying changes in cursor position.
+ /// </summary>
+ UseMovedCursorV2 = 0xE
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs
new file mode 100644
index 00000000..b21db507
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs
@@ -0,0 +1,93 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Possible responses from the software keyboard when running in inline mode.
+ /// </summary>
+ enum InlineKeyboardResponse : uint
+ {
+ /// <summary>
+ /// The software keyboard received a Calc and it is fully initialized. Reply data is ignored by the user-process.
+ /// </summary>
+ FinishedInitialize = 0x0,
+
+ /// <summary>
+ /// Default response. Official sw has no handling for this besides just closing the storage.
+ /// </summary>
+ Default = 0x1,
+
+ /// <summary>
+ /// The text data in the software keyboard changed (UTF-16 encoding).
+ /// </summary>
+ ChangedString = 0x2,
+
+ /// <summary>
+ /// The cursor position in the software keyboard changed (UTF-16 encoding).
+ /// </summary>
+ MovedCursor = 0x3,
+
+ /// <summary>
+ /// A tab in the software keyboard changed.
+ /// </summary>
+ MovedTab = 0x4,
+
+ /// <summary>
+ /// The OK key was pressed in the software keyboard, confirming the input text (UTF-16 encoding).
+ /// </summary>
+ DecidedEnter = 0x5,
+
+ /// <summary>
+ /// The Cancel key was pressed in the software keyboard, cancelling the input.
+ /// </summary>
+ DecidedCancel = 0x6,
+
+ /// <summary>
+ /// Same as ChangedString, but with UTF-8 encoding.
+ /// </summary>
+ ChangedStringUtf8 = 0x7,
+
+ /// <summary>
+ /// Same as MovedCursor, but with UTF-8 encoding.
+ /// </summary>
+ MovedCursorUtf8 = 0x8,
+
+ /// <summary>
+ /// Same as DecidedEnter, but with UTF-8 encoding.
+ /// </summary>
+ DecidedEnterUtf8 = 0x9,
+
+ /// <summary>
+ /// They software keyboard is releasing the data previously set by a SetCustomizeDic request.
+ /// </summary>
+ UnsetCustomizeDic = 0xA,
+
+ /// <summary>
+ /// They software keyboard is releasing the data previously set by a SetUserWordInfo request.
+ /// </summary>
+ ReleasedUserWordInfo = 0xB,
+
+ /// <summary>
+ /// They software keyboard is releasing the data previously set by a SetCustomizedDictionaries request.
+ /// </summary>
+ UnsetCustomizedDictionaries = 0xC,
+
+ /// <summary>
+ /// Same as ChangedString, but with additional fields.
+ /// </summary>
+ ChangedStringV2 = 0xD,
+
+ /// <summary>
+ /// Same as MovedCursor, but with additional fields.
+ /// </summary>
+ MovedCursorV2 = 0xE,
+
+ /// <summary>
+ /// Same as ChangedStringUtf8, but with additional fields.
+ /// </summary>
+ ChangedStringUtf8V2 = 0xF,
+
+ /// <summary>
+ /// Same as MovedCursorUtf8, but with additional fields.
+ /// </summary>
+ MovedCursorUtf8V2 = 0x10
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs
new file mode 100644
index 00000000..47e1a774
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Possible states for the software keyboard when running in inline mode.
+ /// </summary>
+ enum InlineKeyboardState : uint
+ {
+ /// <summary>
+ /// The software keyboard has just been created or finalized and is uninitialized.
+ /// </summary>
+ Uninitialized = 0x0,
+
+ /// <summary>
+ /// The software keyboard is initialized, but it is not visible and not processing input.
+ /// </summary>
+ Initialized = 0x1,
+
+ /// <summary>
+ /// The software keyboard is transitioning to a visible state.
+ /// </summary>
+ Appearing = 0x2,
+
+ /// <summary>
+ /// The software keyboard is visible and receiving processing input.
+ /// </summary>
+ Shown = 0x3,
+
+ /// <summary>
+ /// software keyboard is transitioning to a hidden state because the user pressed either OK or Cancel.
+ /// </summary>
+ Disappearing = 0x4
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs
new file mode 100644
index 00000000..c3e45d46
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs
@@ -0,0 +1,298 @@
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ internal class InlineResponses
+ {
+ private const uint MaxStrLenUTF8 = 0x7D4;
+ private const uint MaxStrLenUTF16 = 0x3EC;
+
+ private static void BeginResponse(InlineKeyboardState state, InlineKeyboardResponse resCode, BinaryWriter writer)
+ {
+ writer.Write((uint)state);
+ writer.Write((uint)resCode);
+ }
+
+ private static uint WriteString(string text, BinaryWriter writer, uint maxSize, Encoding encoding)
+ {
+ // Ensure the text fits in the buffer, but do not straight cut the bytes because
+ // this may corrupt the encoding. Search for a cut in the source string that fits.
+
+ byte[] bytes = null;
+
+ for (int maxStr = text.Length; maxStr >= 0; maxStr--)
+ {
+ // This loop will probably will run only once.
+ bytes = encoding.GetBytes(text, 0, maxStr);
+ if (bytes.Length <= maxSize)
+ {
+ break;
+ }
+ }
+
+ writer.Write(bytes);
+ writer.Seek((int)maxSize - bytes.Length, SeekOrigin.Current);
+ writer.Write((uint)text.Length); // String size
+
+ return (uint)text.Length; // Return the cursor position at the end of the text
+ }
+
+ private static void WriteStringWithCursor(string text, uint cursor, BinaryWriter writer, uint maxSize, Encoding encoding, bool padMiddle)
+ {
+ uint length = WriteString(text, writer, maxSize, encoding);
+
+ if (cursor > length)
+ {
+ cursor = length;
+ }
+
+ if (padMiddle)
+ {
+ writer.Write((int)-1); // ?
+ writer.Write((int)-1); // ?
+ }
+
+ writer.Write(cursor); // Cursor position
+ }
+
+ public static byte[] FinishedInitialize(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint) + 0x1;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.FinishedInitialize, writer);
+ writer.Write((byte)1); // Data (ignored by the program)
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] Default(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint);
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.Default, writer);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] ChangedString(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 6 * sizeof(uint) + MaxStrLenUTF16;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.ChangedString, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, true);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] MovedCursor(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.MovedCursor, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] MovedTab(string text, uint cursor, InlineKeyboardState state)
+ {
+ // Should be the same as MovedCursor.
+
+ uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.MovedTab, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] DecidedEnter(string text, InlineKeyboardState state)
+ {
+ uint resSize = 3 * sizeof(uint) + MaxStrLenUTF16;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.DecidedEnter, writer);
+ WriteString(text, writer, MaxStrLenUTF16, Encoding.Unicode);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] DecidedCancel(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint);
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.DecidedCancel, writer);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] ChangedStringUtf8(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 6 * sizeof(uint) + MaxStrLenUTF8;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.ChangedStringUtf8, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, true);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] MovedCursorUtf8(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 4 * sizeof(uint) + MaxStrLenUTF8;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.MovedCursorUtf8, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, false);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] DecidedEnterUtf8(string text, InlineKeyboardState state)
+ {
+ uint resSize = 3 * sizeof(uint) + MaxStrLenUTF8;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.DecidedEnterUtf8, writer);
+ WriteString(text, writer, MaxStrLenUTF8, Encoding.UTF8);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] UnsetCustomizeDic(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint);
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.UnsetCustomizeDic, writer);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] ReleasedUserWordInfo(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint);
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.ReleasedUserWordInfo, writer);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] UnsetCustomizedDictionaries(InlineKeyboardState state)
+ {
+ uint resSize = 2 * sizeof(uint);
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.UnsetCustomizedDictionaries, writer);
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] ChangedStringV2(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 6 * sizeof(uint) + MaxStrLenUTF16 + 0x1;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.ChangedStringV2, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, true);
+ writer.Write((byte)0); // Flag == 0
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] MovedCursorV2(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16 + 0x1;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.MovedCursorV2, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false);
+ writer.Write((byte)0); // Flag == 0
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] ChangedStringUtf8V2(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 6 * sizeof(uint) + MaxStrLenUTF8 + 0x1;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.ChangedStringUtf8V2, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, true);
+ writer.Write((byte)0); // Flag == 0
+
+ return stream.ToArray();
+ }
+ }
+
+ public static byte[] MovedCursorUtf8V2(string text, uint cursor, InlineKeyboardState state)
+ {
+ uint resSize = 4 * sizeof(uint) + MaxStrLenUTF8 + 0x1;
+
+ using (MemoryStream stream = new MemoryStream(new byte[resSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ BeginResponse(state, InlineKeyboardResponse.MovedCursorUtf8V2, writer);
+ WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, false);
+ writer.Write((byte)0); // Flag == 0
+
+ return stream.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs
new file mode 100644
index 00000000..c3ce2c12
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies the text entry mode.
+ /// </summary>
+ enum InputFormMode : uint
+ {
+ /// <summary>
+ /// Displays the text entry area as a single-line field.
+ /// </summary>
+ SingleLine,
+
+ /// <summary>
+ /// Displays the text entry area as a multi-line field.
+ /// </summary>
+ MultiLine
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs
new file mode 100644
index 00000000..1166e81d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies prohibited buttons.
+ /// </summary>
+ [Flags]
+ enum InvalidButtonFlags : uint
+ {
+ None = 0,
+ AnalogStickL = 1 << 1,
+ AnalogStickR = 1 << 2,
+ ZL = 1 << 3,
+ ZR = 1 << 4,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs
new file mode 100644
index 00000000..f3fd8ac8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs
@@ -0,0 +1,56 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies prohibited character sets.
+ /// </summary>
+ [Flags]
+ enum InvalidCharFlags : uint
+ {
+ /// <summary>
+ /// No characters are prohibited.
+ /// </summary>
+ None = 0 << 1,
+
+ /// <summary>
+ /// Prohibits spaces.
+ /// </summary>
+ Space = 1 << 1,
+
+ /// <summary>
+ /// Prohibits the at (@) symbol.
+ /// </summary>
+ AtSymbol = 1 << 2,
+
+ /// <summary>
+ /// Prohibits the percent (%) symbol.
+ /// </summary>
+ Percent = 1 << 3,
+
+ /// <summary>
+ /// Prohibits the forward slash (/) symbol.
+ /// </summary>
+ ForwardSlash = 1 << 4,
+
+ /// <summary>
+ /// Prohibits the backward slash (\) symbol.
+ /// </summary>
+ BackSlash = 1 << 5,
+
+ /// <summary>
+ /// Prohibits numbers.
+ /// </summary>
+ Numbers = 1 << 6,
+
+ /// <summary>
+ /// Prohibits characters outside of those allowed in download codes.
+ /// </summary>
+ DownloadCode = 1 << 7,
+
+ /// <summary>
+ /// Prohibits characters outside of those allowed in Mii Nicknames.
+ /// </summary>
+ Username = 1 << 8
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs
new file mode 100644
index 00000000..0b0f138b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Bitmask of commands encoded in the Flags field of the Calc structs.
+ /// </summary>
+ [Flags]
+ enum KeyboardCalcFlags : ulong
+ {
+ Initialize = 0x1,
+ SetVolume = 0x2,
+ Appear = 0x4,
+ SetInputText = 0x8,
+ SetCursorPos = 0x10,
+ SetUtf8Mode = 0x20,
+ SetKeyboardBackground = 0x100,
+ SetKeyboardOptions1 = 0x200,
+ SetKeyboardOptions2 = 0x800,
+ EnableSeGroup = 0x2000,
+ DisableSeGroup = 0x4000,
+ SetBackspaceEnabled = 0x8000,
+ AppearTrigger = 0x10000,
+ MustShow = Appear | SetInputText | AppearTrigger
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs
new file mode 100644
index 00000000..925d52f6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Active input options set by the keyboard applet. These options allow keyboard
+ /// players to input text without conflicting with the controller mappings.
+ /// </summary>
+ enum KeyboardInputMode : uint
+ {
+ ControllerAndKeyboard,
+ KeyboardOnly,
+ ControllerOnly,
+ Count,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs
new file mode 100644
index 00000000..5184118c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// The miniaturization mode used by the keyboard in inline mode.
+ /// </summary>
+ enum KeyboardMiniaturizationMode : byte
+ {
+ None = 0,
+ Auto = 1,
+ Forced = 2
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs
new file mode 100644
index 00000000..f512050e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs
@@ -0,0 +1,31 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies the variant of keyboard displayed on screen.
+ /// </summary>
+ enum KeyboardMode : uint
+ {
+ /// <summary>
+ /// A full alpha-numeric keyboard.
+ /// </summary>
+ Default = 0,
+
+ /// <summary>
+ /// Number pad.
+ /// </summary>
+ NumbersOnly = 1,
+
+ /// <summary>
+ /// ASCII characters keyboard.
+ /// </summary>
+ ASCII = 2,
+
+ FullLatin = 3,
+ Alphabet = 4,
+ SimplifiedChinese = 5,
+ TraditionalChinese = 6,
+ Korean = 7,
+ LanguageSet2 = 8,
+ LanguageSet2Latin = 9,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs
new file mode 100644
index 00000000..4f570d3f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// The intention of the user when they finish the interaction with the keyboard.
+ /// </summary>
+ enum KeyboardResult
+ {
+ NotSet = 0,
+ Accept = 1,
+ Cancel = 2,
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs
new file mode 100644
index 00000000..fc9e1ff8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies the display mode of text in a password field.
+ /// </summary>
+ enum PasswordMode : uint
+ {
+ /// <summary>
+ /// Display input characters.
+ /// </summary>
+ Disabled,
+
+ /// <summary>
+ /// Hide input characters.
+ /// </summary>
+ Enabled
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png
new file mode 100644
index 00000000..a8ee784d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png
Binary files differ
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg
new file mode 100644
index 00000000..6257fd12
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="32"
+ height="32"
+ viewBox="0 0 8.4666665 8.4666669"
+ version="1.1"
+ id="svg8"
+ inkscape:export-filename="C:\Users\caian\source\repos\Ryujinx\Ryujinx.HLE\HOS\Applets\SoftwareKeyboard\Resources\Icon_Accept.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ sodipodi:docname="buttons_ab.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="15.839192"
+ inkscape:cx="16.591066"
+ inkscape:cy="14.090021"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ units="px"
+ showguides="false"
+ inkscape:window-width="1267"
+ inkscape:window-height="976"
+ inkscape:window-x="242"
+ inkscape:window-y="34"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <circle
+ style="fill:#ffffff;stroke-width:1.57002;fill-opacity:1"
+ id="path839"
+ cx="4.2333331"
+ cy="4.2333331"
+ r="4.2333331" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:7.02011px;line-height:1.25;font-family:sans-serif;fill:#4b4b4b;fill-opacity:1;stroke:none;stroke-width:0.376071"
+ x="1.9222834"
+ y="6.5921373"
+ id="text835-2"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ inkscape:export-filename="C:\Users\caian\source\repos\Ryujinx\Ryujinx.HLE\HOS\Applets\SoftwareKeyboard\Resources\text835-2.png"><tspan
+ sodipodi:role="line"
+ id="tspan833-9"
+ x="1.9222834"
+ y="6.5921373"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.02011px;font-family:Arial;-inkscape-font-specification:Arial;fill:#4b4b4b;fill-opacity:1;stroke-width:0.376071">A</tspan></text>
+ </g>
+</svg>
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png
new file mode 100644
index 00000000..e1fa3454
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png
Binary files differ
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg
new file mode 100644
index 00000000..ea6bb9bd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="32"
+ height="32"
+ viewBox="0 0 8.4666665 8.4666669"
+ version="1.1"
+ id="svg8"
+ inkscape:export-filename="C:\Users\caian\source\repos\Ryujinx\Ryujinx.HLE\HOS\Applets\SoftwareKeyboard\Resources\Icon_Accept.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ sodipodi:docname="buttons_ab.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="15.839192"
+ inkscape:cx="16.591066"
+ inkscape:cy="14.090021"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ units="px"
+ showguides="false"
+ inkscape:window-width="1267"
+ inkscape:window-height="976"
+ inkscape:window-x="242"
+ inkscape:window-y="34"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <circle
+ style="fill:#ffffff;stroke-width:1.57002;fill-opacity:1"
+ id="path839"
+ cx="4.2333331"
+ cy="4.2333331"
+ r="4.2333331" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:7.02012px;line-height:1.25;font-family:sans-serif;fill:#4b4b4b;fill-opacity:1;stroke:none;stroke-width:0.37607"
+ x="2.0223334"
+ y="6.6920195"
+ id="text835"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"><tspan
+ sodipodi:role="line"
+ id="tspan833"
+ x="2.0223334"
+ y="6.6920195"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.02012px;font-family:Arial;-inkscape-font-specification:Arial;fill:#4b4b4b;fill-opacity:1;stroke-width:0.37607">B</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:7.02011px;line-height:1.25;font-family:sans-serif;fill:#4b4b4b;fill-opacity:1;stroke:none;stroke-width:0.376071"
+ x="2.0223367"
+ y="6.6920156"
+ id="text835-2"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ inkscape:export-filename="C:\Users\caian\source\repos\Ryujinx\Ryujinx.HLE\HOS\Applets\SoftwareKeyboard\Resources\text835-2.png"><tspan
+ sodipodi:role="line"
+ id="tspan833-9"
+ x="2.0223367"
+ y="6.6920156"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.02011px;font-family:Arial;-inkscape-font-specification:Arial;fill:#4b4b4b;fill-opacity:1;stroke-width:0.376071">B</tspan></text>
+ </g>
+</svg>
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png
new file mode 100644
index 00000000..d6dbdc1a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png
Binary files differ
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg
new file mode 100644
index 00000000..2256ebeb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="32"
+ height="32"
+ viewBox="0 0 8.4666665 8.4666669"
+ version="1.1"
+ id="svg8"
+ inkscape:export-filename="C:\Users\caian\source\repos\Ryujinx\Ryujinx.HLE\HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF5.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ sodipodi:docname="Icon_KeyF5.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="15.839192"
+ inkscape:cx="16.591066"
+ inkscape:cy="14.090021"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ inkscape:document-rotation="0"
+ showgrid="false"
+ units="px"
+ showguides="false"
+ inkscape:window-width="1267"
+ inkscape:window-height="976"
+ inkscape:window-x="242"
+ inkscape:window-y="25"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:#ffffff;stroke-width:2.21199"
+ id="rect837"
+ width="8.4666662"
+ height="8.4666662"
+ x="1.3877788e-17"
+ y="0" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:4.23333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264578"
+ x="1.0762799"
+ y="4.2016153"
+ id="text835"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ transform="scale(0.9999825,1.0000175)"><tspan
+ sodipodi:role="line"
+ id="tspan833"
+ x="1.0762799"
+ y="4.2016153"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;font-family:Consolas;-inkscape-font-specification:Consolas;stroke-width:0.264578">F6</tspan></text>
+ <rect
+ style="fill:none;fill-opacity:1;stroke:#757575;stroke-width:0.26458333;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ id="rect891"
+ width="6.9844265"
+ height="6.984426"
+ x="0.74112016"
+ y="0.47653681" />
+ <path
+ style="fill:none;stroke:#757575;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 0,0 0.74112016,0.47653681"
+ id="path895"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#757575;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.4666662,0 7.7255461,0.47653681"
+ id="path897"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#757575;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 7.3685303e-7,8.4666667 0.7411209,7.4609628"
+ id="path901"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#757575;stroke-width:0.264583px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.4666669,8.4666667 7.7255468,7.4609628"
+ id="path903"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png
new file mode 100644
index 00000000..0e8da15e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png
Binary files differ
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs
new file mode 100644
index 00000000..e1ee0507
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs
@@ -0,0 +1,119 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure with appearance configurations for the software keyboard when running in inline mode.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardAppear
+ {
+ public const int OkTextLength = SoftwareKeyboardAppearEx.OkTextLength;
+
+ public KeyboardMode KeyboardMode;
+
+ /// <summary>
+ /// The string displayed in the Submit button.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = OkTextLength + 1)]
+ public string OkText;
+
+ /// <summary>
+ /// The character displayed in the left button of the numeric keyboard.
+ /// </summary>
+ public char LeftOptionalSymbolKey;
+
+ /// <summary>
+ /// The character displayed in the right button of the numeric keyboard.
+ /// </summary>
+ public char RightOptionalSymbolKey;
+
+ /// <summary>
+ /// When set, predictive typing is enabled making use of the system dictionary, and any custom user dictionary.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PredictionEnabled;
+
+ /// <summary>
+ /// When set, there is only the option to accept the input.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool CancelButtonDisabled;
+
+ /// <summary>
+ /// Specifies prohibited characters that cannot be input into the text entry area.
+ /// </summary>
+ public InvalidCharFlags InvalidChars;
+
+ /// <summary>
+ /// Maximum text length allowed.
+ /// </summary>
+ public int TextMaxLength;
+
+ /// <summary>
+ /// Minimum text length allowed.
+ /// </summary>
+ public int TextMinLength;
+
+ /// <summary>
+ /// Indicates the return button is enabled in the keyboard. This allows for input with multiple lines.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseNewLine;
+
+ /// <summary>
+ /// [10.0.0+] If value is 1 or 2, then keytopAsFloating=0 and footerScalable=1 in Calc.
+ /// </summary>
+ public KeyboardMiniaturizationMode MiniaturizationMode;
+
+ public byte Reserved1;
+ public byte Reserved2;
+
+ /// <summary>
+ /// Bit field with invalid buttons for the keyboard.
+ /// </summary>
+ public InvalidButtonFlags InvalidButtons;
+
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseSaveData;
+
+ public uint Reserved3;
+ public ushort Reserved4;
+ public byte Reserved5;
+ public ulong Reserved6;
+ public ulong Reserved7;
+
+ public SoftwareKeyboardAppearEx ToExtended()
+ {
+ SoftwareKeyboardAppearEx appear = new SoftwareKeyboardAppearEx();
+
+ appear.KeyboardMode = KeyboardMode;
+ appear.OkText = OkText;
+ appear.LeftOptionalSymbolKey = LeftOptionalSymbolKey;
+ appear.RightOptionalSymbolKey = RightOptionalSymbolKey;
+ appear.PredictionEnabled = PredictionEnabled;
+ appear.CancelButtonDisabled = CancelButtonDisabled;
+ appear.InvalidChars = InvalidChars;
+ appear.TextMaxLength = TextMaxLength;
+ appear.TextMinLength = TextMinLength;
+ appear.UseNewLine = UseNewLine;
+ appear.MiniaturizationMode = MiniaturizationMode;
+ appear.Reserved1 = Reserved1;
+ appear.Reserved2 = Reserved2;
+ appear.InvalidButtons = InvalidButtons;
+ appear.UseSaveData = UseSaveData;
+ appear.Reserved3 = Reserved3;
+ appear.Reserved4 = Reserved4;
+ appear.Reserved5 = Reserved5;
+ appear.Uid0 = Reserved6;
+ appear.Uid1 = Reserved7;
+ appear.SamplingNumber = 0;
+ appear.Reserved6 = 0;
+ appear.Reserved7 = 0;
+ appear.Reserved8 = 0;
+ appear.Reserved9 = 0;
+
+ return appear;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs
new file mode 100644
index 00000000..d1756b07
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs
@@ -0,0 +1,100 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure with appearance configurations for the software keyboard when running in inline mode.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardAppearEx
+ {
+ public const int OkTextLength = 8;
+
+ public KeyboardMode KeyboardMode;
+
+ /// <summary>
+ /// The string displayed in the Submit button.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = OkTextLength + 1)]
+ public string OkText;
+
+ /// <summary>
+ /// The character displayed in the left button of the numeric keyboard.
+ /// </summary>
+ public char LeftOptionalSymbolKey;
+
+ /// <summary>
+ /// The character displayed in the right button of the numeric keyboard.
+ /// </summary>
+ public char RightOptionalSymbolKey;
+
+ /// <summary>
+ /// When set, predictive typing is enabled making use of the system dictionary, and any custom user dictionary.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PredictionEnabled;
+
+ /// <summary>
+ /// When set, there is only the option to accept the input.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool CancelButtonDisabled;
+
+ /// <summary>
+ /// Specifies prohibited characters that cannot be input into the text entry area.
+ /// </summary>
+ public InvalidCharFlags InvalidChars;
+
+ /// <summary>
+ /// Maximum text length allowed.
+ /// </summary>
+ public int TextMaxLength;
+
+ /// <summary>
+ /// Minimum text length allowed.
+ /// </summary>
+ public int TextMinLength;
+
+ /// <summary>
+ /// Indicates the return button is enabled in the keyboard. This allows for input with multiple lines.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseNewLine;
+
+ /// <summary>
+ /// [10.0.0+] If value is 1 or 2, then keytopAsFloating=0 and footerScalable=1 in Calc.
+ /// </summary>
+ public KeyboardMiniaturizationMode MiniaturizationMode;
+
+ public byte Reserved1;
+ public byte Reserved2;
+
+ /// <summary>
+ /// Bit field with invalid buttons for the keyboard.
+ /// </summary>
+ public InvalidButtonFlags InvalidButtons;
+
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseSaveData;
+
+ public uint Reserved3;
+ public ushort Reserved4;
+ public byte Reserved5;
+
+ /// <summary>
+ /// The id of the user associated with the appear request.
+ /// </summary>
+ public ulong Uid0;
+ public ulong Uid1;
+
+ /// <summary>
+ /// The sampling number for the keyboard appearance.
+ /// </summary>
+ public ulong SamplingNumber;
+
+ public ulong Reserved6;
+ public ulong Reserved7;
+ public ulong Reserved8;
+ public ulong Reserved9;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
new file mode 100644
index 00000000..278ea56c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
@@ -0,0 +1,816 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+using Ryujinx.HLE.Ui;
+using Ryujinx.HLE.Ui.Input;
+using Ryujinx.Memory;
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ internal class SoftwareKeyboardApplet : IApplet
+ {
+ private const string DefaultInputText = "Ryujinx";
+
+ private const int StandardBufferSize = 0x7D8;
+ private const int InteractiveBufferSize = 0x7D4;
+ private const int MaxUserWords = 0x1388;
+ private const int MaxUiTextSize = 100;
+
+ private const Key CycleInputModesKey = Key.F6;
+
+ private readonly Switch _device;
+
+ private SoftwareKeyboardState _foregroundState = SoftwareKeyboardState.Uninitialized;
+ private volatile InlineKeyboardState _backgroundState = InlineKeyboardState.Uninitialized;
+
+ private bool _isBackground = false;
+
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
+
+ // Configuration for foreground mode.
+ private SoftwareKeyboardConfig _keyboardForegroundConfig;
+
+ // Configuration for background (inline) mode.
+ private SoftwareKeyboardInitialize _keyboardBackgroundInitialize;
+ private SoftwareKeyboardCustomizeDic _keyboardBackgroundDic;
+ private SoftwareKeyboardDictSet _keyboardBackgroundDictSet;
+ private SoftwareKeyboardUserWord[] _keyboardBackgroundUserWords;
+
+ private byte[] _transferMemory;
+
+ private string _textValue = "";
+ private int _cursorBegin = 0;
+ private Encoding _encoding = Encoding.Unicode;
+ private KeyboardResult _lastResult = KeyboardResult.NotSet;
+
+ private IDynamicTextInputHandler _dynamicTextInputHandler = null;
+ private SoftwareKeyboardRenderer _keyboardRenderer = null;
+ private NpadReader _npads = null;
+ private bool _canAcceptController = false;
+ private KeyboardInputMode _inputMode = KeyboardInputMode.ControllerAndKeyboard;
+
+ private object _lock = new object();
+
+ public event EventHandler AppletStateChanged;
+
+ public SoftwareKeyboardApplet(Horizon system)
+ {
+ _device = system.Device;
+ }
+
+ public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+ {
+ lock (_lock)
+ {
+ _normalSession = normalSession;
+ _interactiveSession = interactiveSession;
+
+ _interactiveSession.DataAvailable += OnInteractiveData;
+
+ var launchParams = _normalSession.Pop();
+ var keyboardConfig = _normalSession.Pop();
+
+ _isBackground = keyboardConfig.Length == Unsafe.SizeOf<SoftwareKeyboardInitialize>();
+
+ if (_isBackground)
+ {
+ // Initialize the keyboard applet in background mode.
+
+ _keyboardBackgroundInitialize = MemoryMarshal.Read<SoftwareKeyboardInitialize>(keyboardConfig);
+ _backgroundState = InlineKeyboardState.Uninitialized;
+
+ if (_device.UiHandler == null)
+ {
+ Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly");
+ }
+ else
+ {
+ // Create a text handler that converts keyboard strokes to strings.
+ _dynamicTextInputHandler = _device.UiHandler.CreateDynamicTextInputHandler();
+ _dynamicTextInputHandler.TextChangedEvent += HandleTextChangedEvent;
+ _dynamicTextInputHandler.KeyPressedEvent += HandleKeyPressedEvent;
+
+ _npads = new NpadReader(_device);
+ _npads.NpadButtonDownEvent += HandleNpadButtonDownEvent;
+ _npads.NpadButtonUpEvent += HandleNpadButtonUpEvent;
+
+ _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UiHandler.HostUiTheme);
+ }
+
+ return ResultCode.Success;
+ }
+ else
+ {
+ // Initialize the keyboard applet in foreground mode.
+
+ if (keyboardConfig.Length < Marshal.SizeOf<SoftwareKeyboardConfig>())
+ {
+ Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}");
+ }
+ else
+ {
+ _keyboardForegroundConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
+ }
+
+ if (!_normalSession.TryPop(out _transferMemory))
+ {
+ Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null");
+ }
+
+ if (_keyboardForegroundConfig.UseUtf8)
+ {
+ _encoding = Encoding.UTF8;
+ }
+
+ _foregroundState = SoftwareKeyboardState.Ready;
+
+ ExecuteForegroundKeyboard();
+
+ return ResultCode.Success;
+ }
+ }
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+
+ private bool IsKeyboardActive()
+ {
+ return _backgroundState >= InlineKeyboardState.Appearing && _backgroundState < InlineKeyboardState.Disappearing;
+ }
+
+ private bool InputModeControllerEnabled()
+ {
+ return _inputMode == KeyboardInputMode.ControllerAndKeyboard ||
+ _inputMode == KeyboardInputMode.ControllerOnly;
+ }
+
+ private bool InputModeTypingEnabled()
+ {
+ return _inputMode == KeyboardInputMode.ControllerAndKeyboard ||
+ _inputMode == KeyboardInputMode.KeyboardOnly;
+ }
+
+ private void AdvanceInputMode()
+ {
+ _inputMode = (KeyboardInputMode)((int)(_inputMode + 1) % (int)KeyboardInputMode.Count);
+ }
+
+ public bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position)
+ {
+ _npads?.Update();
+
+ _keyboardRenderer?.SetSurfaceInfo(surfaceInfo);
+
+ return _keyboardRenderer?.DrawTo(destination, position) ?? false;
+ }
+
+ private void ExecuteForegroundKeyboard()
+ {
+ string initialText = null;
+
+ // Initial Text is always encoded as a UTF-16 string in the work buffer (passed as transfer memory)
+ // InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters
+ if (_transferMemory != null && _keyboardForegroundConfig.InitialStringLength > 0)
+ {
+ initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardForegroundConfig.InitialStringOffset,
+ 2 * _keyboardForegroundConfig.InitialStringLength);
+ }
+
+ // If the max string length is 0, we set it to a large default
+ // length.
+ if (_keyboardForegroundConfig.StringLengthMax == 0)
+ {
+ _keyboardForegroundConfig.StringLengthMax = 100;
+ }
+
+ if (_device.UiHandler == null)
+ {
+ Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default");
+
+ _textValue = DefaultInputText;
+ _lastResult = KeyboardResult.Accept;
+ }
+ else
+ {
+ // Call the configured GUI handler to get user's input.
+ var args = new SoftwareKeyboardUiArgs
+ {
+ HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
+ SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
+ GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),
+ SubmitText = (!string.IsNullOrWhiteSpace(_keyboardForegroundConfig.SubmitText) ?
+ _keyboardForegroundConfig.SubmitText : "OK"),
+ StringLengthMin = _keyboardForegroundConfig.StringLengthMin,
+ StringLengthMax = _keyboardForegroundConfig.StringLengthMax,
+ InitialText = initialText
+ };
+
+ _lastResult = _device.UiHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel;
+ _textValue ??= initialText ?? DefaultInputText;
+ }
+
+ // If the game requests a string with a minimum length less
+ // than our default text, repeat our default text until we meet
+ // the minimum length requirement.
+ // This should always be done before the text truncation step.
+ while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin)
+ {
+ _textValue = String.Join(" ", _textValue, _textValue);
+ }
+
+ // If our default text is longer than the allowed length,
+ // we truncate it.
+ if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
+ {
+ _textValue = _textValue.Substring(0, _keyboardForegroundConfig.StringLengthMax);
+ }
+
+ // Does the application want to validate the text itself?
+ if (_keyboardForegroundConfig.CheckText)
+ {
+ // The application needs to validate the response, so we
+ // submit it to the interactive output buffer, and poll it
+ // for validation. Once validated, the application will submit
+ // back a validation status, which is handled in OnInteractiveDataPushIn.
+ _foregroundState = SoftwareKeyboardState.ValidationPending;
+
+ PushForegroundResponse(true);
+ }
+ else
+ {
+ // If the application doesn't need to validate the response,
+ // we push the data to the non-interactive output buffer
+ // and poll it for completion.
+ _foregroundState = SoftwareKeyboardState.Complete;
+
+ PushForegroundResponse(false);
+
+ AppletStateChanged?.Invoke(this, null);
+ }
+ }
+
+ private void OnInteractiveData(object sender, EventArgs e)
+ {
+ // Obtain the validation status response.
+ var data = _interactiveSession.Pop();
+
+ if (_isBackground)
+ {
+ lock (_lock)
+ {
+ OnBackgroundInteractiveData(data);
+ }
+ }
+ else
+ {
+ OnForegroundInteractiveData(data);
+ }
+ }
+
+ private void OnForegroundInteractiveData(byte[] data)
+ {
+ if (_foregroundState == SoftwareKeyboardState.ValidationPending)
+ {
+ // TODO(jduncantor):
+ // If application rejects our "attempt", submit another attempt,
+ // and put the applet back in PendingValidation state.
+
+ // For now we assume success, so we push the final result
+ // to the standard output buffer and carry on our merry way.
+ PushForegroundResponse(false);
+
+ AppletStateChanged?.Invoke(this, null);
+
+ _foregroundState = SoftwareKeyboardState.Complete;
+ }
+ else if (_foregroundState == SoftwareKeyboardState.Complete)
+ {
+ // If we have already completed, we push the result text
+ // back on the output buffer and poll the application.
+ PushForegroundResponse(false);
+
+ AppletStateChanged?.Invoke(this, null);
+ }
+ else
+ {
+ // We shouldn't be able to get here through standard swkbd execution.
+ throw new InvalidOperationException("Software Keyboard is in an invalid state.");
+ }
+ }
+
+ private void OnBackgroundInteractiveData(byte[] data)
+ {
+ // WARNING: Only invoke applet state changes after an explicit finalization
+ // request from the game, this is because the inline keyboard is expected to
+ // keep running in the background sending data by itself.
+
+ using (MemoryStream stream = new MemoryStream(data))
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ var request = (InlineKeyboardRequest)reader.ReadUInt32();
+
+ long remaining;
+
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Keyboard received command {request} in state {_backgroundState}");
+
+ switch (request)
+ {
+ case InlineKeyboardRequest.UseChangedStringV2:
+ Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseChangedStringV2");
+ break;
+ case InlineKeyboardRequest.UseMovedCursorV2:
+ Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseMovedCursorV2");
+ break;
+ case InlineKeyboardRequest.SetUserWordInfo:
+ // Read the user word info data.
+ remaining = stream.Length - stream.Position;
+ if (remaining < sizeof(int))
+ {
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info of {remaining} bytes");
+ }
+ else
+ {
+ int wordsCount = reader.ReadInt32();
+ int wordSize = Unsafe.SizeOf<SoftwareKeyboardUserWord>();
+ remaining = stream.Length - stream.Position;
+
+ if (wordsCount > MaxUserWords)
+ {
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Received {wordsCount} User Words but the maximum is {MaxUserWords}");
+ }
+ else if (wordsCount * wordSize != remaining)
+ {
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info data of {remaining} bytes for {wordsCount} words");
+ }
+ else
+ {
+ _keyboardBackgroundUserWords = new SoftwareKeyboardUserWord[wordsCount];
+
+ for (int word = 0; word < wordsCount; word++)
+ {
+ _keyboardBackgroundUserWords[word] = reader.ReadStruct<SoftwareKeyboardUserWord>();
+ }
+ }
+ }
+ _interactiveSession.Push(InlineResponses.ReleasedUserWordInfo(_backgroundState));
+ break;
+ case InlineKeyboardRequest.SetCustomizeDic:
+ // Read the custom dic data.
+ remaining = stream.Length - stream.Position;
+ if (remaining != Unsafe.SizeOf<SoftwareKeyboardCustomizeDic>())
+ {
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes");
+ }
+ else
+ {
+ _keyboardBackgroundDic = reader.ReadStruct<SoftwareKeyboardCustomizeDic>();
+ }
+ break;
+ case InlineKeyboardRequest.SetCustomizedDictionaries:
+ // Read the custom dictionaries data.
+ remaining = stream.Length - stream.Position;
+ if (remaining != Unsafe.SizeOf<SoftwareKeyboardDictSet>())
+ {
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes");
+ }
+ else
+ {
+ _keyboardBackgroundDictSet = reader.ReadStruct<SoftwareKeyboardDictSet>();
+ }
+ break;
+ case InlineKeyboardRequest.Calc:
+ // The Calc request is used to communicate configuration changes and commands to the keyboard.
+ // Fields in the Calc struct and operations are masked by the Flags field.
+
+ // Read the Calc data.
+ SoftwareKeyboardCalcEx newCalc;
+ remaining = stream.Length - stream.Position;
+ if (remaining == Marshal.SizeOf<SoftwareKeyboardCalc>())
+ {
+ var keyboardCalcData = reader.ReadBytes((int)remaining);
+ var keyboardCalc = ReadStruct<SoftwareKeyboardCalc>(keyboardCalcData);
+
+ newCalc = keyboardCalc.ToExtended();
+ }
+ else if (remaining == Marshal.SizeOf<SoftwareKeyboardCalcEx>() || remaining == SoftwareKeyboardCalcEx.AlternativeSize)
+ {
+ var keyboardCalcData = reader.ReadBytes((int)remaining);
+
+ newCalc = ReadStruct<SoftwareKeyboardCalcEx>(keyboardCalcData);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Calc of {remaining} bytes");
+
+ newCalc = new SoftwareKeyboardCalcEx();
+ }
+
+ // Process each individual operation specified in the flags.
+
+ bool updateText = false;
+
+ if ((newCalc.Flags & KeyboardCalcFlags.Initialize) != 0)
+ {
+ _interactiveSession.Push(InlineResponses.FinishedInitialize(_backgroundState));
+
+ _backgroundState = InlineKeyboardState.Initialized;
+ }
+
+ if ((newCalc.Flags & KeyboardCalcFlags.SetCursorPos) != 0)
+ {
+ _cursorBegin = newCalc.CursorPos;
+ updateText = true;
+
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Cursor position set to {_cursorBegin}");
+ }
+
+ if ((newCalc.Flags & KeyboardCalcFlags.SetInputText) != 0)
+ {
+ _textValue = newCalc.InputText;
+ updateText = true;
+
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Input text set to {_textValue}");
+ }
+
+ if ((newCalc.Flags & KeyboardCalcFlags.SetUtf8Mode) != 0)
+ {
+ _encoding = newCalc.UseUtf8 ? Encoding.UTF8 : Encoding.Default;
+
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Encoding set to {_encoding}");
+ }
+
+ if (updateText)
+ {
+ _dynamicTextInputHandler.SetText(_textValue, _cursorBegin);
+ _keyboardRenderer.UpdateTextState(_textValue, _cursorBegin, _cursorBegin, null, null);
+ }
+
+ if ((newCalc.Flags & KeyboardCalcFlags.MustShow) != 0)
+ {
+ ActivateFrontend();
+
+ _backgroundState = InlineKeyboardState.Shown;
+
+ PushChangedString(_textValue, (uint)_cursorBegin, _backgroundState);
+ }
+
+ // Send the response to the Calc
+ _interactiveSession.Push(InlineResponses.Default(_backgroundState));
+ break;
+ case InlineKeyboardRequest.Finalize:
+ // Destroy the frontend.
+ DestroyFrontend();
+ // The calling application wants to close the keyboard applet and will wait for a state change.
+ _backgroundState = InlineKeyboardState.Uninitialized;
+ AppletStateChanged?.Invoke(this, null);
+ break;
+ default:
+ // We shouldn't be able to get here through standard swkbd execution.
+ Logger.Warning?.Print(LogClass.ServiceAm, $"Invalid Software Keyboard request {request} during state {_backgroundState}");
+ _interactiveSession.Push(InlineResponses.Default(_backgroundState));
+ break;
+ }
+ }
+ }
+
+ private void ActivateFrontend()
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Activating software keyboard frontend");
+
+ _inputMode = KeyboardInputMode.ControllerAndKeyboard;
+
+ _npads.Update(true);
+
+ NpadButton buttons = _npads.GetCurrentButtonsOfAllNpads();
+
+ // Block the input if the current accept key is pressed so the applet won't be instantly closed.
+ _canAcceptController = (buttons & NpadButton.A) == 0;
+
+ _dynamicTextInputHandler.TextProcessingEnabled = true;
+
+ _keyboardRenderer.UpdateCommandState(null, null, true);
+ _keyboardRenderer.UpdateTextState(null, null, null, null, true);
+ }
+
+ private void DeactivateFrontend()
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Deactivating software keyboard frontend");
+
+ _inputMode = KeyboardInputMode.ControllerAndKeyboard;
+ _canAcceptController = false;
+
+ _dynamicTextInputHandler.TextProcessingEnabled = false;
+ _dynamicTextInputHandler.SetText(_textValue, _cursorBegin);
+ }
+
+ private void DestroyFrontend()
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Destroying software keyboard frontend");
+
+ _keyboardRenderer?.Dispose();
+ _keyboardRenderer = null;
+
+ if (_dynamicTextInputHandler != null)
+ {
+ _dynamicTextInputHandler.TextChangedEvent -= HandleTextChangedEvent;
+ _dynamicTextInputHandler.KeyPressedEvent -= HandleKeyPressedEvent;
+ _dynamicTextInputHandler.Dispose();
+ _dynamicTextInputHandler = null;
+ }
+
+ if (_npads != null)
+ {
+ _npads.NpadButtonDownEvent -= HandleNpadButtonDownEvent;
+ _npads.NpadButtonUpEvent -= HandleNpadButtonUpEvent;
+ _npads = null;
+ }
+ }
+
+ private bool HandleKeyPressedEvent(Key key)
+ {
+ if (key == CycleInputModesKey)
+ {
+ lock (_lock)
+ {
+ if (IsKeyboardActive())
+ {
+ AdvanceInputMode();
+
+ bool typingEnabled = InputModeTypingEnabled();
+ bool controllerEnabled = InputModeControllerEnabled();
+
+ _dynamicTextInputHandler.TextProcessingEnabled = typingEnabled;
+
+ _keyboardRenderer.UpdateTextState(null, null, null, null, typingEnabled);
+ _keyboardRenderer.UpdateCommandState(null, null, controllerEnabled);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void HandleTextChangedEvent(string text, int cursorBegin, int cursorEnd, bool overwriteMode)
+ {
+ lock (_lock)
+ {
+ // Text processing should not run with typing disabled.
+ Debug.Assert(InputModeTypingEnabled());
+
+ if (text.Length > MaxUiTextSize)
+ {
+ // Limit the text size and change it back.
+ text = text.Substring(0, MaxUiTextSize);
+ cursorBegin = Math.Min(cursorBegin, MaxUiTextSize);
+ cursorEnd = Math.Min(cursorEnd, MaxUiTextSize);
+
+ _dynamicTextInputHandler.SetText(text, cursorBegin, cursorEnd);
+ }
+
+ _textValue = text;
+ _cursorBegin = cursorBegin;
+ _keyboardRenderer.UpdateTextState(text, cursorBegin, cursorEnd, overwriteMode, null);
+
+ PushUpdatedState(text, cursorBegin, KeyboardResult.NotSet);
+ }
+ }
+
+ private void HandleNpadButtonDownEvent(int npadIndex, NpadButton button)
+ {
+ lock (_lock)
+ {
+ if (!IsKeyboardActive())
+ {
+ return;
+ }
+
+ switch (button)
+ {
+ case NpadButton.A:
+ _keyboardRenderer.UpdateCommandState(_canAcceptController, null, null);
+ break;
+ case NpadButton.B:
+ _keyboardRenderer.UpdateCommandState(null, _canAcceptController, null);
+ break;
+ }
+ }
+ }
+
+ private void HandleNpadButtonUpEvent(int npadIndex, NpadButton button)
+ {
+ lock (_lock)
+ {
+ KeyboardResult result = KeyboardResult.NotSet;
+
+ switch (button)
+ {
+ case NpadButton.A:
+ result = KeyboardResult.Accept;
+ _keyboardRenderer.UpdateCommandState(false, null, null);
+ break;
+ case NpadButton.B:
+ result = KeyboardResult.Cancel;
+ _keyboardRenderer.UpdateCommandState(null, false, null);
+ break;
+ }
+
+ if (IsKeyboardActive())
+ {
+ if (!_canAcceptController)
+ {
+ _canAcceptController = true;
+ }
+ else if (InputModeControllerEnabled())
+ {
+ PushUpdatedState(_textValue, _cursorBegin, result);
+ }
+ }
+ }
+ }
+
+ private void PushUpdatedState(string text, int cursorBegin, KeyboardResult result)
+ {
+ _lastResult = result;
+ _textValue = text;
+
+ bool cancel = result == KeyboardResult.Cancel;
+ bool accept = result == KeyboardResult.Accept;
+
+ if (!IsKeyboardActive())
+ {
+ // Keyboard is not active.
+
+ return;
+ }
+
+ if (accept == false && cancel == false)
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Updating keyboard text to {text} and cursor position to {cursorBegin}");
+
+ PushChangedString(text, (uint)cursorBegin, _backgroundState);
+ }
+ else
+ {
+ // Disable the frontend.
+ DeactivateFrontend();
+
+ // The 'Complete' state indicates the Calc request has been fulfilled by the applet.
+ _backgroundState = InlineKeyboardState.Disappearing;
+
+ if (accept)
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Sending keyboard OK with text {text}");
+
+ DecidedEnter(text, _backgroundState);
+ }
+ else if (cancel)
+ {
+ Logger.Debug?.Print(LogClass.ServiceAm, "Sending keyboard Cancel");
+
+ DecidedCancel(_backgroundState);
+ }
+
+ _interactiveSession.Push(InlineResponses.Default(_backgroundState));
+
+ Logger.Debug?.Print(LogClass.ServiceAm, $"Resetting state of the keyboard to {_backgroundState}");
+
+ // Set the state of the applet to 'Initialized' as it is the only known state so far
+ // that does not soft-lock the keyboard after use.
+
+ _backgroundState = InlineKeyboardState.Initialized;
+
+ _interactiveSession.Push(InlineResponses.Default(_backgroundState));
+ }
+ }
+
+ private void PushChangedString(string text, uint cursor, InlineKeyboardState state)
+ {
+ // TODO (Caian): The *V2 methods are not supported because the applications that request
+ // them do not seem to accept them. The regular methods seem to work just fine in all cases.
+
+ if (_encoding == Encoding.UTF8)
+ {
+ _interactiveSession.Push(InlineResponses.ChangedStringUtf8(text, cursor, state));
+ }
+ else
+ {
+ _interactiveSession.Push(InlineResponses.ChangedString(text, cursor, state));
+ }
+ }
+
+ private void DecidedEnter(string text, InlineKeyboardState state)
+ {
+ if (_encoding == Encoding.UTF8)
+ {
+ _interactiveSession.Push(InlineResponses.DecidedEnterUtf8(text, state));
+ }
+ else
+ {
+ _interactiveSession.Push(InlineResponses.DecidedEnter(text, state));
+ }
+ }
+
+ private void DecidedCancel(InlineKeyboardState state)
+ {
+ _interactiveSession.Push(InlineResponses.DecidedCancel(state));
+ }
+
+ private void PushForegroundResponse(bool interactive)
+ {
+ int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize;
+
+ using (MemoryStream stream = new MemoryStream(new byte[bufferSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ byte[] output = _encoding.GetBytes(_textValue);
+
+ if (!interactive)
+ {
+ // Result Code.
+ writer.Write(_lastResult == KeyboardResult.Accept ? 0U : 1U);
+ }
+ else
+ {
+ // In interactive mode, we write the length of the text as a long, rather than
+ // a result code. This field is inclusive of the 64-bit size.
+ writer.Write((long)output.Length + 8);
+ }
+
+ writer.Write(output);
+
+ if (!interactive)
+ {
+ _normalSession.Push(stream.ToArray());
+ }
+ else
+ {
+ _interactiveSession.Push(stream.ToArray());
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes all Unicode control code characters from the input string.
+ /// This includes CR/LF, tabs, null characters, escape characters,
+ /// and special control codes which are used for formatting by the real keyboard applet.
+ /// </summary>
+ /// <remarks>
+ /// Some games send special control codes (such as 0x13 "Device Control 3") as part of the string.
+ /// Future implementations of the emulated keyboard applet will need to handle these as well.
+ /// </remarks>
+ /// <param name="input">The input string to sanitize (may be null).</param>
+ /// <returns>The sanitized string.</returns>
+ internal static string StripUnicodeControlCodes(string input)
+ {
+ if (input is null)
+ {
+ return null;
+ }
+
+ if (input.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ StringBuilder sb = new StringBuilder(capacity: input.Length);
+ foreach (char c in input)
+ {
+ if (!char.IsControl(c))
+ {
+ sb.Append(c);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ private static T ReadStruct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(byte[] data)
+ where T : struct
+ {
+ GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
+
+ try
+ {
+ return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
+ }
+ finally
+ {
+ handle.Free();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs
new file mode 100644
index 00000000..90df6fa3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs
@@ -0,0 +1,220 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure with configuration options of the software keyboard when starting a new input request in inline mode.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardCalc
+ {
+ public const int InputTextLength = SoftwareKeyboardCalcEx.InputTextLength;
+
+ public uint Unknown;
+
+ /// <summary>
+ /// The size of the Calc struct, as reported by the process communicating with the applet.
+ /// </summary>
+ public ushort Size;
+
+ public byte Unknown1;
+ public byte Unknown2;
+
+ /// <summary>
+ /// Configuration flags. Each bit in the bitfield enabled a different operation of the keyboard
+ /// using the data provided with the Calc structure.
+ /// </summary>
+ public KeyboardCalcFlags Flags;
+
+ /// <summary>
+ /// The original parameters used when initializing the keyboard applet.
+ /// Flag: 0x1
+ /// </summary>
+ public SoftwareKeyboardInitialize Initialize;
+
+ /// <summary>
+ /// The audio volume used by the sound effects of the keyboard.
+ /// Flag: 0x2
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// The initial position of the text cursor (caret) in the provided input text.
+ /// Flag: 0x10
+ /// </summary>
+ public int CursorPos;
+
+ /// <summary>
+ /// Appearance configurations for the on-screen keyboard.
+ /// </summary>
+ public SoftwareKeyboardAppear Appear;
+
+ /// <summary>
+ /// The initial input text to be used by the software keyboard.
+ /// Flag: 0x8
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = InputTextLength + 1)]
+ public string InputText;
+
+ /// <summary>
+ /// When set, the strings communicated by software keyboard will be encoded as UTF-8 instead of UTF-16.
+ /// Flag: 0x20
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseUtf8;
+
+ public byte Unknown3;
+
+ /// <summary>
+ /// [5.0.0+] Enable the backspace key in the software keyboard.
+ /// Flag: 0x8000
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool BackspaceEnabled;
+
+ public short Unknown4;
+ public byte Unknown5;
+
+ /// <summary>
+ /// Flag: 0x200
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool KeytopAsFloating;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool FooterScalable;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool AlphaEnabledInInputMode;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ public byte InputModeFadeType;
+
+ /// <summary>
+ /// When set, the software keyboard ignores touch input.
+ /// Flag: 0x200
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool TouchDisabled;
+
+ /// <summary>
+ /// When set, the software keyboard ignores hardware keyboard commands.
+ /// Flag: 0x800
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool HardwareKeyboardDisabled;
+
+ public uint Unknown6;
+ public uint Unknown7;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float KeytopScale0;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float KeytopScale1;
+
+ public float KeytopTranslate0;
+ public float KeytopTranslate1;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x100
+ /// </summary>
+ public float KeytopBgAlpha;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x100
+ /// </summary>
+ public float FooterBgAlpha;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float BalloonScale;
+
+ public float Unknown8;
+ public uint Unknown9;
+ public uint Unknown10;
+ public uint Unknown11;
+
+ /// <summary>
+ /// [5.0.0+] Enable sound effect.
+ /// Flag: Enable: 0x2000
+ /// Disable: 0x4000
+ /// </summary>
+ public byte SeGroup;
+
+ /// <summary>
+ /// [6.0.0+] Enables the Trigger field when Trigger is non-zero.
+ /// </summary>
+ public byte TriggerFlag;
+
+ /// <summary>
+ /// [6.0.0+] Always set to zero.
+ /// </summary>
+ public byte Trigger;
+
+ public byte Padding;
+
+ public SoftwareKeyboardCalcEx ToExtended()
+ {
+ SoftwareKeyboardCalcEx calc = new SoftwareKeyboardCalcEx();
+
+ calc.Unknown = Unknown;
+ calc.Size = Size;
+ calc.Unknown1 = Unknown1;
+ calc.Unknown2 = Unknown2;
+ calc.Flags = Flags;
+ calc.Initialize = Initialize;
+ calc.Volume = Volume;
+ calc.CursorPos = CursorPos;
+ calc.Appear = Appear.ToExtended();
+ calc.InputText = InputText;
+ calc.UseUtf8 = UseUtf8;
+ calc.Unknown3 = Unknown3;
+ calc.BackspaceEnabled = BackspaceEnabled;
+ calc.Unknown4 = Unknown4;
+ calc.Unknown5 = Unknown5;
+ calc.KeytopAsFloating = KeytopAsFloating;
+ calc.FooterScalable = FooterScalable;
+ calc.AlphaEnabledInInputMode = AlphaEnabledInInputMode;
+ calc.InputModeFadeType = InputModeFadeType;
+ calc.TouchDisabled = TouchDisabled;
+ calc.HardwareKeyboardDisabled = HardwareKeyboardDisabled;
+ calc.Unknown6 = Unknown6;
+ calc.Unknown7 = Unknown7;
+ calc.KeytopScale0 = KeytopScale0;
+ calc.KeytopScale1 = KeytopScale1;
+ calc.KeytopTranslate0 = KeytopTranslate0;
+ calc.KeytopTranslate1 = KeytopTranslate1;
+ calc.KeytopBgAlpha = KeytopBgAlpha;
+ calc.FooterBgAlpha = FooterBgAlpha;
+ calc.BalloonScale = BalloonScale;
+ calc.Unknown8 = Unknown8;
+ calc.Unknown9 = Unknown9;
+ calc.Unknown10 = Unknown10;
+ calc.Unknown11 = Unknown11;
+ calc.SeGroup = SeGroup;
+ calc.TriggerFlag = TriggerFlag;
+ calc.Trigger = Trigger;
+
+ return calc;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs
new file mode 100644
index 00000000..2d3d5dbe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs
@@ -0,0 +1,182 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure with configuration options of the software keyboard when starting a new input request in inline mode.
+ /// This is the extended version of the structure with extended appear options.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardCalcEx
+ {
+ /// <summary>
+ /// This struct was built following Switchbrew's specs, but this size (larger) is also found in real games.
+ /// It's assumed that this is padding at the end of this struct, because all members seem OK.
+ /// </summary>
+ public const int AlternativeSize = 1256;
+
+ public const int InputTextLength = 505;
+
+ public uint Unknown;
+
+ /// <summary>
+ /// The size of the Calc struct, as reported by the process communicating with the applet.
+ /// </summary>
+ public ushort Size;
+
+ public byte Unknown1;
+ public byte Unknown2;
+
+ /// <summary>
+ /// Configuration flags. Each bit in the bitfield enabled a different operation of the keyboard
+ /// using the data provided with the Calc structure.
+ /// </summary>
+ public KeyboardCalcFlags Flags;
+
+ /// <summary>
+ /// The original parameters used when initializing the keyboard applet.
+ /// Flag: 0x1
+ /// </summary>
+ public SoftwareKeyboardInitialize Initialize;
+
+ /// <summary>
+ /// The audio volume used by the sound effects of the keyboard.
+ /// Flag: 0x2
+ /// </summary>
+ public float Volume;
+
+ /// <summary>
+ /// The initial position of the text cursor (caret) in the provided input text.
+ /// Flag: 0x10
+ /// </summary>
+ public int CursorPos;
+
+ /// <summary>
+ /// Appearance configurations for the on-screen keyboard.
+ /// </summary>
+ public SoftwareKeyboardAppearEx Appear;
+
+ /// <summary>
+ /// The initial input text to be used by the software keyboard.
+ /// Flag: 0x8
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = InputTextLength + 1)]
+ public string InputText;
+
+ /// <summary>
+ /// When set, the strings communicated by software keyboard will be encoded as UTF-8 instead of UTF-16.
+ /// Flag: 0x20
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseUtf8;
+
+ public byte Unknown3;
+
+ /// <summary>
+ /// [5.0.0+] Enable the backspace key in the software keyboard.
+ /// Flag: 0x8000
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool BackspaceEnabled;
+
+ public short Unknown4;
+ public byte Unknown5;
+
+ /// <summary>
+ /// Flag: 0x200
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool KeytopAsFloating;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool FooterScalable;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool AlphaEnabledInInputMode;
+
+ /// <summary>
+ /// Flag: 0x100
+ /// </summary>
+ public byte InputModeFadeType;
+
+ /// <summary>
+ /// When set, the software keyboard ignores touch input.
+ /// Flag: 0x200
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool TouchDisabled;
+
+ /// <summary>
+ /// When set, the software keyboard ignores hardware keyboard commands.
+ /// Flag: 0x800
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool HardwareKeyboardDisabled;
+
+ public uint Unknown6;
+ public uint Unknown7;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float KeytopScale0;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float KeytopScale1;
+
+ public float KeytopTranslate0;
+ public float KeytopTranslate1;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x100
+ /// </summary>
+ public float KeytopBgAlpha;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x100
+ /// </summary>
+ public float FooterBgAlpha;
+
+ /// <summary>
+ /// Default value is 1.0.
+ /// Flag: 0x200
+ /// </summary>
+ public float BalloonScale;
+
+ public float Unknown8;
+ public uint Unknown9;
+ public uint Unknown10;
+ public uint Unknown11;
+
+ /// <summary>
+ /// [5.0.0+] Enable sound effect.
+ /// Flag: Enable: 0x2000
+ /// Disable: 0x4000
+ /// </summary>
+ public byte SeGroup;
+
+ /// <summary>
+ /// [6.0.0+] Enables the Trigger field when Trigger is non-zero.
+ /// </summary>
+ public byte TriggerFlag;
+
+ /// <summary>
+ /// [6.0.0+] Always set to zero.
+ /// </summary>
+ public byte Trigger;
+
+ public byte Padding;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs
new file mode 100644
index 00000000..fd462382
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs
@@ -0,0 +1,138 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure that defines the configuration options of the software keyboard.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardConfig
+ {
+ private const int SubmitTextLength = 8;
+ private const int HeaderTextLength = 64;
+ private const int SubtitleTextLength = 128;
+ private const int GuideTextLength = 256;
+
+ /// <summary>
+ /// Type of keyboard.
+ /// </summary>
+ public KeyboardMode Mode;
+
+ /// <summary>
+ /// The string displayed in the Submit button.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)]
+ public string SubmitText;
+
+ /// <summary>
+ /// The character displayed in the left button of the numeric keyboard.
+ /// This is ignored when Mode is not set to NumbersOnly.
+ /// </summary>
+ public char LeftOptionalSymbolKey;
+
+ /// <summary>
+ /// The character displayed in the right button of the numeric keyboard.
+ /// This is ignored when Mode is not set to NumbersOnly.
+ /// </summary>
+ public char RightOptionalSymbolKey;
+
+ /// <summary>
+ /// When set, predictive typing is enabled making use of the system dictionary,
+ /// and any custom user dictionary.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PredictionEnabled;
+
+ /// <summary>
+ /// Specifies prohibited characters that cannot be input into the text entry area.
+ /// </summary>
+ public InvalidCharFlags InvalidCharFlag;
+
+ /// <summary>
+ /// The initial position of the text cursor displayed in the text entry area.
+ /// </summary>
+ public InitialCursorPosition InitialCursorPosition;
+
+ /// <summary>
+ /// The string displayed in the header area of the keyboard.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)]
+ public string HeaderText;
+
+ /// <summary>
+ /// The string displayed in the subtitle area of the keyboard.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)]
+ public string SubtitleText;
+
+ /// <summary>
+ /// The placeholder string displayed in the text entry area when no text is entered.
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)]
+ public string GuideText;
+
+ /// <summary>
+ /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area.
+ /// </summary>
+ public int StringLengthMax;
+
+ /// <summary>
+ /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area.
+ /// </summary>
+ public int StringLengthMin;
+
+ /// <summary>
+ /// When enabled, hides input characters as dots in the text entry area.
+ /// </summary>
+ public PasswordMode PasswordMode;
+
+ /// <summary>
+ /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field.
+ /// </summary>
+ public InputFormMode InputFormMode;
+
+ /// <summary>
+ /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseNewLine;
+
+ /// <summary>
+ /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseUtf8;
+
+ /// <summary>
+ /// When set, the software keyboard will blur the game application rendered behind the keyboard.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UseBlurBackground;
+
+ /// <summary>
+ /// Offset into the work buffer of the initial text when the keyboard is first displayed.
+ /// </summary>
+ public int InitialStringOffset;
+
+ /// <summary>
+ /// Length of the initial text.
+ /// </summary>
+ public int InitialStringLength;
+
+ /// <summary>
+ /// Offset into the work buffer of the custom user dictionary.
+ /// </summary>
+ public int CustomDictionaryOffset;
+
+ /// <summary>
+ /// Number of entries in the custom user dictionary.
+ /// </summary>
+ public int CustomDictionaryCount;
+
+ /// <summary>
+ /// When set, the text entered will be validated on the application side after the keyboard has been submitted.
+ /// </summary>
+ [MarshalAs(UnmanagedType.I1)]
+ public bool CheckText;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs
new file mode 100644
index 00000000..53c8c895
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure used by SetCustomizeDic request to software keyboard.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x70)]
+ struct SoftwareKeyboardCustomizeDic
+ {
+ // Unknown
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs
new file mode 100644
index 00000000..38554881
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure with custom dictionary words for the software keyboard.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Pack = 2)]
+ struct SoftwareKeyboardDictSet
+ {
+ /// <summary>
+ /// A 0x1000-byte aligned buffer position.
+ /// </summary>
+ public ulong BufferPosition;
+
+ /// <summary>
+ /// A 0x1000-byte aligned buffer size.
+ /// </summary>
+ public uint BufferSize;
+
+ /// <summary>
+ /// Array of word entries in the buffer.
+ /// </summary>
+ public Array24<ulong> Entries;
+
+ /// <summary>
+ /// Number of used entries in the Entries field.
+ /// </summary>
+ public ushort TotalEntries;
+
+ public ushort Padding1;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs
new file mode 100644
index 00000000..764d0e38
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs
@@ -0,0 +1,26 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure that mirrors the parameters used to initialize the keyboard applet.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ struct SoftwareKeyboardInitialize
+ {
+ public uint Unknown;
+
+ /// <summary>
+ /// The applet mode used when launching the swkb. The bits regarding the background vs foreground mode can be wrong.
+ /// </summary>
+ public byte LibMode;
+
+ /// <summary>
+ /// [5.0.0+] Set to 0x1 to indicate a firmware version >= 5.0.0.
+ /// </summary>
+ public byte FivePlus;
+
+ public byte Padding1;
+ public byte Padding2;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
new file mode 100644
index 00000000..c30ad11b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs
@@ -0,0 +1,164 @@
+using Ryujinx.HLE.Ui;
+using Ryujinx.Memory;
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Class that manages the renderer base class and its state in a multithreaded context.
+ /// </summary>
+ internal class SoftwareKeyboardRenderer : IDisposable
+ {
+ private const int TextBoxBlinkSleepMilliseconds = 100;
+ private const int RendererWaitTimeoutMilliseconds = 100;
+
+ private readonly object _stateLock = new object();
+
+ private SoftwareKeyboardUiState _state = new SoftwareKeyboardUiState();
+ private SoftwareKeyboardRendererBase _renderer;
+
+ private TimedAction _textBoxBlinkTimedAction = new TimedAction();
+ private TimedAction _renderAction = new TimedAction();
+
+ public SoftwareKeyboardRenderer(IHostUiTheme uiTheme)
+ {
+ _renderer = new SoftwareKeyboardRendererBase(uiTheme);
+
+ StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock);
+ StartRenderer(_renderAction, _renderer, _state, _stateLock);
+ }
+
+ private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUiState state, object stateLock)
+ {
+ timedAction.Reset(() =>
+ {
+ lock (stateLock)
+ {
+ // The blinker is on half of the time and events such as input
+ // changes can reset the blinker.
+ state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold);
+
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(stateLock);
+ }
+ }, TextBoxBlinkSleepMilliseconds);
+ }
+
+ private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUiState state, object stateLock)
+ {
+ SoftwareKeyboardUiState internalState = new SoftwareKeyboardUiState();
+
+ bool canCreateSurface = false;
+ bool needsUpdate = true;
+
+ timedAction.Reset(() =>
+ {
+ lock (stateLock)
+ {
+ if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds))
+ {
+ return;
+ }
+
+ needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText);
+ needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin);
+ needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd);
+ needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed);
+ needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed);
+ needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode);
+ needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled);
+ needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled);
+ needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter);
+
+ canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null;
+
+ if (canCreateSurface)
+ {
+ internalState.SurfaceInfo = state.SurfaceInfo;
+ }
+ }
+
+ if (canCreateSurface)
+ {
+ renderer.CreateSurface(internalState.SurfaceInfo);
+ }
+
+ if (needsUpdate)
+ {
+ renderer.DrawMutableElements(internalState);
+ renderer.CopyImageToBuffer();
+ needsUpdate = false;
+ }
+ });
+ }
+
+ private static bool UpdateStateField<T>(ref T source, ref T destination) where T : IEquatable<T>
+ {
+ if (!source.Equals(destination))
+ {
+ destination = source;
+ return true;
+ }
+
+ return false;
+ }
+
+#pragma warning disable CS8632
+ public void UpdateTextState(string? inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled)
+#pragma warning restore CS8632
+ {
+ lock (_stateLock)
+ {
+ // Update the parameters that were provided.
+ _state.InputText = inputText != null ? inputText : _state.InputText;
+ _state.CursorBegin = cursorBegin.GetValueOrDefault(_state.CursorBegin);
+ _state.CursorEnd = cursorEnd.GetValueOrDefault(_state.CursorEnd);
+ _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode);
+ _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled);
+
+ // Reset the cursor blink.
+ _state.TextBoxBlinkCounter = 0;
+
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
+ }
+ }
+
+ public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled)
+ {
+ lock (_stateLock)
+ {
+ // Update the parameters that were provided.
+ _state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed);
+ _state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed);
+ _state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled);
+
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
+ }
+ }
+
+ public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo)
+ {
+ lock (_stateLock)
+ {
+ _state.SurfaceInfo = surfaceInfo;
+
+ // Tell the render thread there is something new to render.
+ Monitor.PulseAll(_stateLock);
+ }
+ }
+
+ internal bool DrawTo(IVirtualMemoryManager destination, ulong position)
+ {
+ return _renderer.WriteBufferToMemory(destination, position);
+ }
+
+ public void Dispose()
+ {
+ _textBoxBlinkTimedAction.RequestCancel();
+ _renderAction.RequestCancel();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
new file mode 100644
index 00000000..9a91fa32
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
@@ -0,0 +1,606 @@
+using Ryujinx.HLE.Ui;
+using Ryujinx.Memory;
+using SixLabors.Fonts;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Drawing.Processing;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Numerics;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Base class that generates the graphics for the software keyboard applet during inline mode.
+ /// </summary>
+ internal class SoftwareKeyboardRendererBase
+ {
+ public const int TextBoxBlinkThreshold = 8;
+
+ const string MessageText = "Please use the keyboard to input text";
+ const string AcceptText = "Accept";
+ const string CancelText = "Cancel";
+ const string ControllerToggleText = "Toggle input";
+
+ private readonly object _bufferLock = new object();
+
+ private RenderingSurfaceInfo _surfaceInfo = null;
+ private Image<Argb32> _surface = null;
+ private byte[] _bufferData = null;
+
+ private Image _ryujinxLogo = null;
+ private Image _padAcceptIcon = null;
+ private Image _padCancelIcon = null;
+ private Image _keyModeIcon = null;
+
+ private float _textBoxOutlineWidth;
+ private float _padPressedPenWidth;
+
+ private Color _textNormalColor;
+ private Color _textSelectedColor;
+ private Color _textOverCursorColor;
+
+ private IBrush _panelBrush;
+ private IBrush _disabledBrush;
+ private IBrush _cursorBrush;
+ private IBrush _selectionBoxBrush;
+
+ private Pen _textBoxOutlinePen;
+ private Pen _cursorPen;
+ private Pen _selectionBoxPen;
+ private Pen _padPressedPen;
+
+ private int _inputTextFontSize;
+ private Font _messageFont;
+ private Font _inputTextFont;
+ private Font _labelsTextFont;
+
+ private RectangleF _panelRectangle;
+ private Point _logoPosition;
+ private float _messagePositionY;
+
+ public SoftwareKeyboardRendererBase(IHostUiTheme uiTheme)
+ {
+ int ryujinxLogoSize = 32;
+
+ string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
+ _ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
+
+ string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
+ string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
+ string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png";
+
+ _padAcceptIcon = LoadResource(Assembly.GetExecutingAssembly(), padAcceptIconPath , 0, 0);
+ _padCancelIcon = LoadResource(Assembly.GetExecutingAssembly(), padCancelIconPath , 0, 0);
+ _keyModeIcon = LoadResource(Assembly.GetExecutingAssembly(), keyModeIconPath , 0, 0);
+
+ Color panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255);
+ Color panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150);
+ Color borderColor = ToColor(uiTheme.DefaultBorderColor);
+ Color selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor);
+
+ _textNormalColor = ToColor(uiTheme.DefaultForegroundColor);
+ _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor);
+ _textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true);
+
+ float cursorWidth = 2;
+
+ _textBoxOutlineWidth = 2;
+ _padPressedPenWidth = 2;
+
+ _panelBrush = new SolidBrush(panelColor);
+ _disabledBrush = new SolidBrush(panelTransparentColor);
+ _cursorBrush = new SolidBrush(_textNormalColor);
+ _selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
+
+ _textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
+ _cursorPen = new Pen(_textNormalColor, cursorWidth);
+ _selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
+ _padPressedPen = new Pen(borderColor, _padPressedPenWidth);
+
+ _inputTextFontSize = 20;
+
+ CreateFonts(uiTheme.FontFamily);
+ }
+
+ private void CreateFonts(string uiThemeFontFamily)
+ {
+ // Try a list of fonts in case any of them is not available in the system.
+
+ string[] availableFonts = new string[]
+ {
+ uiThemeFontFamily,
+ "Liberation Sans",
+ "FreeSans",
+ "DejaVu Sans",
+ "Lucida Grande"
+ };
+
+ foreach (string fontFamily in availableFonts)
+ {
+ try
+ {
+ _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
+ _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
+ _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
+
+ return;
+ }
+ catch
+ {
+ }
+ }
+
+ throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
+ }
+
+ private Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
+ {
+ var a = (byte)(color.A * 255);
+ var r = (byte)(color.R * 255);
+ var g = (byte)(color.G * 255);
+ var b = (byte)(color.B * 255);
+
+ if (flipRgb)
+ {
+ r = (byte)(255 - r);
+ g = (byte)(255 - g);
+ b = (byte)(255 - b);
+ }
+
+ return Color.FromRgba(r, g, b, overrideAlpha.GetValueOrDefault(a));
+ }
+
+ private Image LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight)
+ {
+ Stream resourceStream = assembly.GetManifestResourceStream(resourcePath);
+
+ return LoadResource(resourceStream, newWidth, newHeight);
+ }
+
+ private Image LoadResource(Stream resourceStream, int newWidth, int newHeight)
+ {
+ Debug.Assert(resourceStream != null);
+
+ var image = Image.Load(resourceStream);
+
+ if (newHeight != 0 && newWidth != 0)
+ {
+ image.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Lanczos3));
+ }
+
+ return image;
+ }
+
+ private void SetGraphicsOptions(IImageProcessingContext context)
+ {
+ context.GetGraphicsOptions().Antialias = true;
+ context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
+ }
+
+ private void DrawImmutableElements()
+ {
+ if (_surface == null)
+ {
+ return;
+ }
+
+ _surface.Mutate(context =>
+ {
+ SetGraphicsOptions(context);
+
+ context.Clear(Color.Transparent);
+ context.Fill(_panelBrush, _panelRectangle);
+ context.DrawImage(_ryujinxLogo, _logoPosition, 1);
+
+ float halfWidth = _panelRectangle.Width / 2;
+ float buttonsY = _panelRectangle.Y + 185;
+
+ PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
+
+ DrawControllerToggle(context, disableButtonPosition);
+ });
+ }
+
+ public void DrawMutableElements(SoftwareKeyboardUiState state)
+ {
+ if (_surface == null)
+ {
+ return;
+ }
+
+ _surface.Mutate(context =>
+ {
+ var messageRectangle = MeasureString(MessageText, _messageFont);
+ float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.X;
+ float messagePositionY = _messagePositionY - messageRectangle.Y;
+ var messagePosition = new PointF(messagePositionX, messagePositionY);
+ var messageBoundRectangle = new RectangleF(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height);
+
+ SetGraphicsOptions(context);
+
+ context.Fill(_panelBrush, messageBoundRectangle);
+
+ context.DrawText(MessageText, _messageFont, _textNormalColor, messagePosition);
+
+ if (!state.TypingEnabled)
+ {
+ // Just draw a semi-transparent rectangle on top to fade the component with the background.
+ // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
+
+ context.Fill(_disabledBrush, messageBoundRectangle);
+ }
+
+ DrawTextBox(context, state);
+
+ float halfWidth = _panelRectangle.Width / 2;
+ float buttonsY = _panelRectangle.Y + 185;
+
+ PointF acceptButtonPosition = new PointF(halfWidth - 180, buttonsY);
+ PointF cancelButtonPosition = new PointF(halfWidth , buttonsY);
+ PointF disableButtonPosition = new PointF(halfWidth + 180, buttonsY);
+
+ DrawPadButton(context, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled);
+ DrawPadButton(context, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled);
+ });
+ }
+
+ public void CreateSurface(RenderingSurfaceInfo surfaceInfo)
+ {
+ if (_surfaceInfo != null)
+ {
+ return;
+ }
+
+ _surfaceInfo = surfaceInfo;
+
+ Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8);
+
+ // Use the whole area of the image to draw, even the alignment, otherwise it may shear the final
+ // image if the pitch is different.
+ uint totalWidth = _surfaceInfo.Pitch / 4;
+ uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch;
+
+ Debug.Assert(_surfaceInfo.Width <= totalWidth);
+ Debug.Assert(_surfaceInfo.Height <= totalHeight);
+ Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size);
+
+ _surface = new Image<Argb32>((int)totalWidth, (int)totalHeight);
+
+ ComputeConstants();
+ DrawImmutableElements();
+ }
+
+ private void ComputeConstants()
+ {
+ int totalWidth = (int)_surfaceInfo.Width;
+ int totalHeight = (int)_surfaceInfo.Height;
+
+ int panelHeight = 240;
+ int panelPositionY = totalHeight - panelHeight;
+
+ _panelRectangle = new RectangleF(0, panelPositionY, totalWidth, panelHeight);
+
+ _messagePositionY = panelPositionY + 60;
+
+ int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2;
+ int logoPositionY = panelPositionY + 18;
+
+ _logoPosition = new Point(logoPositionX, logoPositionY);
+ }
+ private static RectangleF MeasureString(string text, Font font)
+ {
+ RendererOptions options = new RendererOptions(font);
+
+ if (text == "")
+ {
+ FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
+
+ return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
+ }
+
+ FontRectangle rectangle = TextMeasurer.Measure(text, options);
+
+ return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
+ }
+
+ private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
+ {
+ RendererOptions options = new RendererOptions(font);
+
+ if (text == "")
+ {
+ FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
+ return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
+ }
+
+ FontRectangle rectangle = TextMeasurer.Measure(text, options);
+
+ return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
+ }
+
+ private void DrawTextBox(IImageProcessingContext context, SoftwareKeyboardUiState state)
+ {
+ var inputTextRectangle = MeasureString(state.InputText, _inputTextFont);
+
+ float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.X + 8));
+ float boxHeight = 32;
+ float boxY = _panelRectangle.Y + 110;
+ float boxX = (int)((_panelRectangle.Width - boxWidth) / 2);
+
+ RectangleF boxRectangle = new RectangleF(boxX, boxY, boxWidth, boxHeight);
+
+ RectangleF boundRectangle = new RectangleF(_panelRectangle.X, boxY - _textBoxOutlineWidth,
+ _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth);
+
+ context.Fill(_panelBrush, boundRectangle);
+
+ context.Draw(_textBoxOutlinePen, boxRectangle);
+
+ float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.X;
+ float inputTextY = boxY + 5;
+
+ var inputTextPosition = new PointF(inputTextX, inputTextY);
+
+ context.DrawText(state.InputText, _inputTextFont, _textNormalColor, inputTextPosition);
+
+ // Draw the cursor on top of the text and redraw the text with a different color if necessary.
+
+ Color cursorTextColor;
+ IBrush cursorBrush;
+ Pen cursorPen;
+
+ float cursorPositionYTop = inputTextY + 1;
+ float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1;
+ float cursorPositionXLeft;
+ float cursorPositionXRight;
+
+ bool cursorVisible = false;
+
+ if (state.CursorBegin != state.CursorEnd)
+ {
+ Debug.Assert(state.InputText.Length > 0);
+
+ cursorTextColor = _textSelectedColor;
+ cursorBrush = _selectionBoxBrush;
+ cursorPen = _selectionBoxPen;
+
+ ReadOnlySpan<char> textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin);
+ ReadOnlySpan<char> textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd);
+
+ var selectionBeginRectangle = MeasureString(textUntilBegin, _inputTextFont);
+ var selectionEndRectangle = MeasureString(textUntilEnd , _inputTextFont);
+
+ cursorVisible = true;
+ cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.X;
+ cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.X;
+ }
+ else
+ {
+ cursorTextColor = _textOverCursorColor;
+ cursorBrush = _cursorBrush;
+ cursorPen = _cursorPen;
+
+ if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold)
+ {
+ // Show the blinking cursor.
+
+ int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin);
+ ReadOnlySpan<char> textUntilCursor = state.InputText.AsSpan(0, cursorBegin);
+ var cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
+
+ cursorVisible = true;
+ cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
+
+ if (state.OverwriteMode)
+ {
+ // The blinking cursor is in overwrite mode so it takes the size of a character.
+
+ if (state.CursorBegin < state.InputText.Length)
+ {
+ textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1);
+ cursorTextRectangle = MeasureString(textUntilCursor, _inputTextFont);
+ cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.X;
+ }
+ else
+ {
+ cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2;
+ }
+ }
+ else
+ {
+ // The blinking cursor is in insert mode so it is only a line.
+ cursorPositionXRight = cursorPositionXLeft;
+ }
+ }
+ else
+ {
+ cursorPositionXLeft = inputTextX;
+ cursorPositionXRight = inputTextX;
+ }
+ }
+
+ if (state.TypingEnabled && cursorVisible)
+ {
+ float cursorWidth = cursorPositionXRight - cursorPositionXLeft;
+ float cursorHeight = cursorPositionYBottom - cursorPositionYTop;
+
+ if (cursorWidth == 0)
+ {
+ PointF[] points = new PointF[]
+ {
+ new PointF(cursorPositionXLeft, cursorPositionYTop),
+ new PointF(cursorPositionXLeft, cursorPositionYBottom),
+ };
+
+ context.DrawLines(cursorPen, points);
+ }
+ else
+ {
+ var cursorRectangle = new RectangleF(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight);
+
+ context.Draw(cursorPen , cursorRectangle);
+ context.Fill(cursorBrush, cursorRectangle);
+
+ Image<Argb32> textOverCursor = new Image<Argb32>((int)cursorRectangle.Width, (int)cursorRectangle.Height);
+ textOverCursor.Mutate(context =>
+ {
+ var textRelativePosition = new PointF(inputTextPosition.X - cursorRectangle.X, inputTextPosition.Y - cursorRectangle.Y);
+ context.DrawText(state.InputText, _inputTextFont, cursorTextColor, textRelativePosition);
+ });
+
+ var cursorPosition = new Point((int)cursorRectangle.X, (int)cursorRectangle.Y);
+ context.DrawImage(textOverCursor, cursorPosition, 1);
+ }
+ }
+ else if (!state.TypingEnabled)
+ {
+ // Just draw a semi-transparent rectangle on top to fade the component with the background.
+ // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
+
+ context.Fill(_disabledBrush, boundRectangle);
+ }
+ }
+
+ private void DrawPadButton(IImageProcessingContext context, PointF point, Image icon, string label, bool pressed, bool enabled)
+ {
+ // Use relative positions so we can center the the entire drawing later.
+
+ float iconX = 0;
+ float iconY = 0;
+ float iconWidth = icon.Width;
+ float iconHeight = icon.Height;
+
+ var labelRectangle = MeasureString(label, _labelsTextFont);
+
+ float labelPositionX = iconWidth + 8 - labelRectangle.X;
+ float labelPositionY = 3;
+
+ float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.X;
+ float fullHeight = iconHeight;
+
+ // Convert all relative positions into absolute.
+
+ float originX = (int)(point.X - fullWidth / 2);
+ float originY = (int)(point.Y - fullHeight / 2);
+
+ iconX += originX;
+ iconY += originY;
+
+ var iconPosition = new Point((int)iconX, (int)iconY);
+ var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
+
+ var selectedRectangle = new RectangleF(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth,
+ fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth);
+
+ var boundRectangle = new RectangleF(originX, originY, fullWidth, fullHeight);
+ boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth);
+
+ context.Fill(_panelBrush, boundRectangle);
+ context.DrawImage(icon, iconPosition, 1);
+ context.DrawText(label, _labelsTextFont, _textNormalColor, labelPosition);
+
+ if (enabled)
+ {
+ if (pressed)
+ {
+ context.Draw(_padPressedPen, selectedRectangle);
+ }
+ }
+ else
+ {
+ // Just draw a semi-transparent rectangle on top to fade the component with the background.
+ // TODO (caian): This will not work if one decides to add make background semi-transparent as well.
+
+ context.Fill(_disabledBrush, boundRectangle);
+ }
+ }
+
+ private void DrawControllerToggle(IImageProcessingContext context, PointF point)
+ {
+ var labelRectangle = MeasureString(ControllerToggleText, _labelsTextFont);
+
+ // Use relative positions so we can center the the entire drawing later.
+
+ float keyWidth = _keyModeIcon.Width;
+ float keyHeight = _keyModeIcon.Height;
+
+ float labelPositionX = keyWidth + 8 - labelRectangle.X;
+ float labelPositionY = -labelRectangle.Y - 1;
+
+ float keyX = 0;
+ float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2);
+
+ float fullWidth = labelPositionX + labelRectangle.Width;
+ float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight);
+
+ // Convert all relative positions into absolute.
+
+ float originX = (int)(point.X - fullWidth / 2);
+ float originY = (int)(point.Y - fullHeight / 2);
+
+ keyX += originX;
+ keyY += originY;
+
+ var labelPosition = new PointF(labelPositionX + originX, labelPositionY + originY);
+ var overlayPosition = new Point((int)keyX, (int)keyY);
+
+ context.DrawImage(_keyModeIcon, overlayPosition, 1);
+ context.DrawText(ControllerToggleText, _labelsTextFont, _textNormalColor, labelPosition);
+ }
+
+ public void CopyImageToBuffer()
+ {
+ lock (_bufferLock)
+ {
+ if (_surface == null)
+ {
+ return;
+ }
+
+ // Convert the pixel format used in the image to the one used in the Switch surface.
+
+ if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
+ {
+ return;
+ }
+
+ _bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
+ Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
+
+ Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
+
+ for (int i = 0; i < dataConvert.Length; i++)
+ {
+ dataConvert[i] = BitOperations.RotateRight(dataConvert[i], 8);
+ }
+ }
+ }
+
+ public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position)
+ {
+ lock (_bufferLock)
+ {
+ if (_bufferData == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ destination.Write(position, _bufferData);
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs
new file mode 100644
index 00000000..0f66fc9b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Identifies the software keyboard state.
+ /// </summary>
+ enum SoftwareKeyboardState
+ {
+ /// <summary>
+ /// swkbd is uninitialized.
+ /// </summary>
+ Uninitialized,
+
+ /// <summary>
+ /// swkbd is ready to process data.
+ /// </summary>
+ Ready,
+
+ /// <summary>
+ /// swkbd is awaiting an interactive reply with a validation status.
+ /// </summary>
+ ValidationPending,
+
+ /// <summary>
+ /// swkbd has completed.
+ /// </summary>
+ Complete
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
new file mode 100644
index 00000000..d24adec3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets
+{
+ public struct SoftwareKeyboardUiArgs
+ {
+ public string HeaderText;
+ public string SubtitleText;
+ public string InitialText;
+ public string GuideText;
+ public string SubmitText;
+ public int StringLengthMin;
+ public int StringLengthMax;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs
new file mode 100644
index 00000000..e6131e62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs
@@ -0,0 +1,22 @@
+using Ryujinx.HLE.Ui;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// TODO
+ /// </summary>
+ internal class SoftwareKeyboardUiState
+ {
+ public string InputText = "";
+ public int CursorBegin = 0;
+ public int CursorEnd = 0;
+ public bool AcceptPressed = false;
+ public bool CancelPressed = false;
+ public bool OverwriteMode = false;
+ public bool TypingEnabled = true;
+ public bool ControllerEnabled = true;
+ public int TextBoxBlinkCounter = 0;
+
+ public RenderingSurfaceInfo SurfaceInfo = null;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs
new file mode 100644
index 00000000..f1bfec2b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A structure used by SetUserWordInfo request to the software keyboard.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, Size = 0x64)]
+ struct SoftwareKeyboardUserWord
+ {
+ // Unknown
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs
new file mode 100644
index 00000000..53746e74
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// Wraps a type in a class so it gets stored in the GC managed heap. This is used as communication mechanism
+ /// between classed that need to be disposed and, thus, can't share their references.
+ /// </summary>
+ /// <typeparam name="T">The internal type.</typeparam>
+ class TRef<T>
+ {
+ public T Value;
+
+ public TRef() { }
+
+ public TRef(T value)
+ {
+ Value = value;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs
new file mode 100644
index 00000000..0de78a0e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ /// <summary>
+ /// A threaded executor of periodic actions that can be cancelled. The total execution time is optional
+ /// and, in this case, a progress is reported back to the action.
+ /// </summary>
+ class TimedAction
+ {
+ public const int MaxThreadSleep = 100;
+
+ private class SleepSubstepData
+ {
+ public readonly int SleepMilliseconds;
+ public readonly int SleepCount;
+ public readonly int SleepRemainderMilliseconds;
+
+ public SleepSubstepData(int sleepMilliseconds)
+ {
+ SleepMilliseconds = Math.Min(sleepMilliseconds, MaxThreadSleep);
+ SleepCount = sleepMilliseconds / SleepMilliseconds;
+ SleepRemainderMilliseconds = sleepMilliseconds - SleepCount * SleepMilliseconds;
+ }
+ }
+
+ private TRef<bool> _cancelled = null;
+ private Thread _thread = null;
+ private object _lock = new object();
+
+ public bool IsRunning
+ {
+ get
+ {
+ lock (_lock)
+ {
+ if (_thread == null)
+ {
+ return false;
+ }
+
+ return _thread.IsAlive;
+ }
+ }
+ }
+
+ public void RequestCancel()
+ {
+ lock (_lock)
+ {
+ if (_cancelled != null)
+ {
+ Volatile.Write(ref _cancelled.Value, true);
+ }
+ }
+ }
+
+ public TimedAction() { }
+
+ private void Reset(Thread thread, TRef<bool> cancelled)
+ {
+ lock (_lock)
+ {
+ // Cancel the current task.
+ if (_cancelled != null)
+ {
+ Volatile.Write(ref _cancelled.Value, true);
+ }
+
+ _cancelled = cancelled;
+
+ _thread = thread;
+ _thread.IsBackground = true;
+ _thread.Start();
+ }
+ }
+
+ public void Reset(Action<float> action, int totalMilliseconds, int sleepMilliseconds)
+ {
+ // Create a dedicated cancel token for each task.
+ var cancelled = new TRef<bool>(false);
+
+ Reset(new Thread(() =>
+ {
+ var substepData = new SleepSubstepData(sleepMilliseconds);
+
+ int totalCount = totalMilliseconds / sleepMilliseconds;
+ int totalRemainder = totalMilliseconds - totalCount * sleepMilliseconds;
+
+ if (Volatile.Read(ref cancelled.Value))
+ {
+ action(-1);
+
+ return;
+ }
+
+ action(0);
+
+ for (int i = 1; i <= totalCount; i++)
+ {
+ if (SleepWithSubstep(substepData, cancelled))
+ {
+ action(-1);
+
+ return;
+ }
+
+ action((float)(i * sleepMilliseconds) / totalMilliseconds);
+ }
+
+ if (totalRemainder > 0)
+ {
+ if (SleepWithSubstep(substepData, cancelled))
+ {
+ action(-1);
+
+ return;
+ }
+
+ action(1);
+ }
+ }), cancelled);
+ }
+
+ public void Reset(Action action, int sleepMilliseconds)
+ {
+ // Create a dedicated cancel token for each task.
+ var cancelled = new TRef<bool>(false);
+
+ Reset(new Thread(() =>
+ {
+ var substepData = new SleepSubstepData(sleepMilliseconds);
+
+ while (!Volatile.Read(ref cancelled.Value))
+ {
+ action();
+
+ if (SleepWithSubstep(substepData, cancelled))
+ {
+ return;
+ }
+ }
+ }), cancelled);
+ }
+
+ public void Reset(Action action)
+ {
+ // Create a dedicated cancel token for each task.
+ var cancelled = new TRef<bool>(false);
+
+ Reset(new Thread(() =>
+ {
+ while (!Volatile.Read(ref cancelled.Value))
+ {
+ action();
+ }
+ }), cancelled);
+ }
+
+ private static bool SleepWithSubstep(SleepSubstepData substepData, TRef<bool> cancelled)
+ {
+ for (int i = 0; i < substepData.SleepCount; i++)
+ {
+ if (Volatile.Read(ref cancelled.Value))
+ {
+ return true;
+ }
+
+ Thread.Sleep(substepData.SleepMilliseconds);
+ }
+
+ if (substepData.SleepRemainderMilliseconds > 0)
+ {
+ if (Volatile.Read(ref cancelled.Value))
+ {
+ return true;
+ }
+
+ Thread.Sleep(substepData.SleepRemainderMilliseconds);
+ }
+
+ return Volatile.Read(ref cancelled.Value);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs
new file mode 100644
index 00000000..6338edc1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs
@@ -0,0 +1,80 @@
+using ARMeilleure.Memory;
+using Ryujinx.Cpu;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS
+{
+ interface IArmProcessContext : IProcessContext
+ {
+ IDiskCacheLoadState Initialize(
+ string titleIdText,
+ string displayVersion,
+ bool diskCacheEnabled,
+ ulong codeAddress,
+ ulong codeSize);
+ }
+
+ class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
+ {
+ private readonly ulong _pid;
+ private readonly GpuContext _gpuContext;
+ private readonly ICpuContext _cpuContext;
+ private T _memoryManager;
+
+ public IVirtualMemoryManager AddressSpace => _memoryManager;
+
+ public ArmProcessContext(ulong pid, ICpuEngine cpuEngine, GpuContext gpuContext, T memoryManager, bool for64Bit)
+ {
+ if (memoryManager is IRefCounted rc)
+ {
+ rc.IncrementReferenceCount();
+ }
+
+ gpuContext.RegisterProcess(pid, memoryManager);
+
+ _pid = pid;
+ _gpuContext = gpuContext;
+ _cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit);
+ _memoryManager = memoryManager;
+ }
+
+ public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
+ {
+ return _cpuContext.CreateExecutionContext(exceptionCallbacks);
+ }
+
+ public void Execute(IExecutionContext context, ulong codeAddress)
+ {
+ _cpuContext.Execute(context, codeAddress);
+ }
+
+ public IDiskCacheLoadState Initialize(
+ string titleIdText,
+ string displayVersion,
+ bool diskCacheEnabled,
+ ulong codeAddress,
+ ulong codeSize)
+ {
+ _cpuContext.PrepareCodeRange(codeAddress, codeSize);
+ return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled);
+ }
+
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ _cpuContext.InvalidateCacheRegion(address, size);
+ }
+
+ public void Dispose()
+ {
+ if (_memoryManager is IRefCounted rc)
+ {
+ rc.DecrementReferenceCount();
+
+ _memoryManager = null;
+ _gpuContext.UnregisterProcess(_pid);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
new file mode 100644
index 00000000..1b0d66ac
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
@@ -0,0 +1,89 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Cpu;
+using Ryujinx.Cpu.AppleHv;
+using Ryujinx.Cpu.Jit;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS
+{
+ class ArmProcessContextFactory : IProcessContextFactory
+ {
+ private readonly ITickSource _tickSource;
+ private readonly GpuContext _gpu;
+ private readonly string _titleIdText;
+ private readonly string _displayVersion;
+ private readonly bool _diskCacheEnabled;
+ private readonly ulong _codeAddress;
+ private readonly ulong _codeSize;
+
+ public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
+
+ public ArmProcessContextFactory(
+ ITickSource tickSource,
+ GpuContext gpu,
+ string titleIdText,
+ string displayVersion,
+ bool diskCacheEnabled,
+ ulong codeAddress,
+ ulong codeSize)
+ {
+ _tickSource = tickSource;
+ _gpu = gpu;
+ _titleIdText = titleIdText;
+ _displayVersion = displayVersion;
+ _diskCacheEnabled = diskCacheEnabled;
+ _codeAddress = codeAddress;
+ _codeSize = codeSize;
+ }
+
+ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
+ {
+ IArmProcessContext processContext;
+
+ if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64 && for64Bit && context.Device.Configuration.UseHypervisor)
+ {
+ var cpuEngine = new HvEngine(_tickSource);
+ var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
+ processContext = new ArmProcessContext<HvMemoryManager>(pid, cpuEngine, _gpu, memoryManager, for64Bit);
+ }
+ else
+ {
+ MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode;
+
+ if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
+ {
+ mode = MemoryManagerMode.SoftwarePageTable;
+ }
+
+ var cpuEngine = new JitEngine(_tickSource);
+
+ switch (mode)
+ {
+ case MemoryManagerMode.SoftwarePageTable:
+ var memoryManager = new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
+ processContext = new ArmProcessContext<MemoryManager>(pid, cpuEngine, _gpu, memoryManager, for64Bit);
+ break;
+
+ case MemoryManagerMode.HostMapped:
+ case MemoryManagerMode.HostMappedUnsafe:
+ bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
+ var memoryManagerHostMapped = new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler);
+ processContext = new ArmProcessContext<MemoryManagerHostMapped>(pid, cpuEngine, _gpu, memoryManagerHostMapped, for64Bit);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize);
+
+ return processContext;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs
new file mode 100644
index 00000000..5145ff7b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs
@@ -0,0 +1,25 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ArraySubscriptingExpression : BaseNode
+ {
+ private BaseNode _leftNode;
+ private BaseNode _subscript;
+
+ public ArraySubscriptingExpression(BaseNode leftNode, BaseNode subscript) : base(NodeType.ArraySubscriptingExpression)
+ {
+ _leftNode = leftNode;
+ _subscript = subscript;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+ _leftNode.Print(writer);
+ writer.Write(")[");
+ _subscript.Print(writer);
+ writer.Write("]");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs
new file mode 100644
index 00000000..4b1041ab
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs
@@ -0,0 +1,59 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ArrayType : BaseNode
+ {
+ private BaseNode _base;
+ private BaseNode _dimensionExpression;
+ private string _dimensionString;
+
+ public ArrayType(BaseNode Base, BaseNode dimensionExpression = null) : base(NodeType.ArrayType)
+ {
+ _base = Base;
+ _dimensionExpression = dimensionExpression;
+ }
+
+ public ArrayType(BaseNode Base, string dimensionString) : base(NodeType.ArrayType)
+ {
+ _base = Base;
+ _dimensionString = dimensionString;
+ }
+
+ public override bool HasRightPart()
+ {
+ return true;
+ }
+
+ public override bool IsArray()
+ {
+ return true;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _base.PrintLeft(writer);
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ // FIXME: detect if previous char was a ].
+ writer.Write(" ");
+
+ writer.Write("[");
+
+ if (_dimensionString != null)
+ {
+ writer.Write(_dimensionString);
+ }
+ else if (_dimensionExpression != null)
+ {
+ _dimensionExpression.Print(writer);
+ }
+
+ writer.Write("]");
+
+ _base.PrintRight(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs
new file mode 100644
index 00000000..ca4b98f8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs
@@ -0,0 +1,113 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public enum NodeType
+ {
+ CvQualifierType,
+ SimpleReferenceType,
+ NameType,
+ EncodedFunction,
+ NestedName,
+ SpecialName,
+ LiteralOperator,
+ NodeArray,
+ ElaboratedType,
+ PostfixQualifiedType,
+ SpecialSubstitution,
+ ExpandedSpecialSubstitution,
+ CtorDtorNameType,
+ EnclosedExpression,
+ ForwardTemplateReference,
+ NameTypeWithTemplateArguments,
+ PackedTemplateArgument,
+ TemplateArguments,
+ BooleanExpression,
+ CastExpression,
+ CallExpression,
+ IntegerCastExpression,
+ PackedTemplateParameter,
+ PackedTemplateParameterExpansion,
+ IntegerLiteral,
+ DeleteExpression,
+ MemberExpression,
+ ArraySubscriptingExpression,
+ InitListExpression,
+ PostfixExpression,
+ ConditionalExpression,
+ ThrowExpression,
+ FunctionParameter,
+ ConversionExpression,
+ BinaryExpression,
+ PrefixExpression,
+ BracedExpression,
+ BracedRangeExpression,
+ NewExpression,
+ QualifiedName,
+ StdQualifiedName,
+ DtOrName,
+ GlobalQualifiedName,
+ NoexceptSpec,
+ DynamicExceptionSpec,
+ FunctionType,
+ PointerType,
+ ReferenceType,
+ ConversionOperatorType,
+ LocalName,
+ CtorVtableSpecialName,
+ ArrayType
+ }
+
+ public abstract class BaseNode
+ {
+ public NodeType Type { get; protected set; }
+
+ public BaseNode(NodeType type)
+ {
+ Type = type;
+ }
+
+ public virtual void Print(TextWriter writer)
+ {
+ PrintLeft(writer);
+
+ if (HasRightPart())
+ {
+ PrintRight(writer);
+ }
+ }
+
+ public abstract void PrintLeft(TextWriter writer);
+
+ public virtual bool HasRightPart()
+ {
+ return false;
+ }
+
+ public virtual bool IsArray()
+ {
+ return false;
+ }
+
+ public virtual bool HasFunctions()
+ {
+ return false;
+ }
+
+ public virtual string GetName()
+ {
+ return null;
+ }
+
+ public virtual void PrintRight(TextWriter writer) {}
+
+ public override string ToString()
+ {
+ StringWriter writer = new StringWriter();
+
+ Print(writer);
+
+ return writer.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs
new file mode 100644
index 00000000..0c492df3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs
@@ -0,0 +1,41 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class BinaryExpression : BaseNode
+ {
+ private BaseNode _leftPart;
+ private string _name;
+ private BaseNode _rightPart;
+
+ public BinaryExpression(BaseNode leftPart, string name, BaseNode rightPart) : base(NodeType.BinaryExpression)
+ {
+ _leftPart = leftPart;
+ _name = name;
+ _rightPart = rightPart;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_name.Equals(">"))
+ {
+ writer.Write("(");
+ }
+
+ writer.Write("(");
+ _leftPart.Print(writer);
+ writer.Write(") ");
+
+ writer.Write(_name);
+
+ writer.Write(" (");
+ _rightPart.Print(writer);
+ writer.Write(")");
+
+ if (_name.Equals(">"))
+ {
+ writer.Write(")");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs
new file mode 100644
index 00000000..6b9782f5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs
@@ -0,0 +1,40 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class BracedExpression : BaseNode
+ {
+ private BaseNode _element;
+ private BaseNode _expression;
+ private bool _isArrayExpression;
+
+ public BracedExpression(BaseNode element, BaseNode expression, bool isArrayExpression) : base(NodeType.BracedExpression)
+ {
+ _element = element;
+ _expression = expression;
+ _isArrayExpression = isArrayExpression;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_isArrayExpression)
+ {
+ writer.Write("[");
+ _element.Print(writer);
+ writer.Write("]");
+ }
+ else
+ {
+ writer.Write(".");
+ _element.Print(writer);
+ }
+
+ if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression))
+ {
+ writer.Write(" = ");
+ }
+
+ _expression.Print(writer);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs
new file mode 100644
index 00000000..802422d9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs
@@ -0,0 +1,34 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class BracedRangeExpression : BaseNode
+ {
+ private BaseNode _firstNode;
+ private BaseNode _lastNode;
+ private BaseNode _expression;
+
+ public BracedRangeExpression(BaseNode firstNode, BaseNode lastNode, BaseNode expression) : base(NodeType.BracedRangeExpression)
+ {
+ _firstNode = firstNode;
+ _lastNode = lastNode;
+ _expression = expression;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("[");
+ _firstNode.Print(writer);
+ writer.Write(" ... ");
+ _lastNode.Print(writer);
+ writer.Write("]");
+
+ if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression))
+ {
+ writer.Write(" = ");
+ }
+
+ _expression.Print(writer);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs
new file mode 100644
index 00000000..8e3fc3e6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class CallExpression : NodeArray
+ {
+ private BaseNode _callee;
+
+ public CallExpression(BaseNode callee, List<BaseNode> nodes) : base(nodes, NodeType.CallExpression)
+ {
+ _callee = callee;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _callee.Print(writer);
+
+ writer.Write("(");
+ writer.Write(string.Join<BaseNode>(", ", Nodes.ToArray()));
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs
new file mode 100644
index 00000000..1149a788
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs
@@ -0,0 +1,28 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class CastExpression : BaseNode
+ {
+ private string _kind;
+ private BaseNode _to;
+ private BaseNode _from;
+
+ public CastExpression(string kind, BaseNode to, BaseNode from) : base(NodeType.CastExpression)
+ {
+ _kind = kind;
+ _to = to;
+ _from = from;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_kind);
+ writer.Write("<");
+ _to.PrintLeft(writer);
+ writer.Write(">(");
+ _from.PrintLeft(writer);
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs
new file mode 100644
index 00000000..c0dd6717
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs
@@ -0,0 +1,29 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ConditionalExpression : BaseNode
+ {
+ private BaseNode _thenNode;
+ private BaseNode _elseNode;
+ private BaseNode _conditionNode;
+
+ public ConditionalExpression(BaseNode conditionNode, BaseNode thenNode, BaseNode elseNode) : base(NodeType.ConditionalExpression)
+ {
+ _thenNode = thenNode;
+ _conditionNode = conditionNode;
+ _elseNode = elseNode;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+ _conditionNode.Print(writer);
+ writer.Write(") ? (");
+ _thenNode.Print(writer);
+ writer.Write(") : (");
+ _elseNode.Print(writer);
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs
new file mode 100644
index 00000000..dd1f7a00
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ConversionExpression : BaseNode
+ {
+ private BaseNode _typeNode;
+ private BaseNode _expressions;
+
+ public ConversionExpression(BaseNode typeNode, BaseNode expressions) : base(NodeType.ConversionExpression)
+ {
+ _typeNode = typeNode;
+ _expressions = expressions;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+ _typeNode.Print(writer);
+ writer.Write(")(");
+ _expressions.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs
new file mode 100644
index 00000000..8a5cde86
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs
@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ConversionOperatorType : ParentNode
+ {
+ public ConversionOperatorType(BaseNode child) : base(NodeType.ConversionOperatorType, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("operator ");
+ Child.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs
new file mode 100644
index 00000000..5f458123
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class CtorDtorNameType : ParentNode
+ {
+ private bool _isDestructor;
+
+ public CtorDtorNameType(BaseNode name, bool isDestructor) : base(NodeType.CtorDtorNameType, name)
+ {
+ _isDestructor = isDestructor;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_isDestructor)
+ {
+ writer.Write("~");
+ }
+
+ writer.Write(Child.GetName());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs
new file mode 100644
index 00000000..3bb5b163
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class CtorVtableSpecialName : BaseNode
+ {
+ private BaseNode _firstType;
+ private BaseNode _secondType;
+
+ public CtorVtableSpecialName(BaseNode firstType, BaseNode secondType) : base(NodeType.CtorVtableSpecialName)
+ {
+ _firstType = firstType;
+ _secondType = secondType;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("construction vtable for ");
+ _firstType.Print(writer);
+ writer.Write("-in-");
+ _secondType.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs
new file mode 100644
index 00000000..14715d25
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs
@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class DeleteExpression : ParentNode
+ {
+ private bool _isGlobal;
+ private bool _isArrayExpression;
+
+ public DeleteExpression(BaseNode child, bool isGlobal, bool isArrayExpression) : base(NodeType.DeleteExpression, child)
+ {
+ _isGlobal = isGlobal;
+ _isArrayExpression = isArrayExpression;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_isGlobal)
+ {
+ writer.Write("::");
+ }
+
+ writer.Write("delete");
+
+ if (_isArrayExpression)
+ {
+ writer.Write("[] ");
+ }
+
+ Child.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs
new file mode 100644
index 00000000..5cc4e6cf
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs
@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class DtorName : ParentNode
+ {
+ public DtorName(BaseNode name) : base(NodeType.DtOrName, name) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("~");
+ Child.PrintLeft(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs
new file mode 100644
index 00000000..faa91443
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs
@@ -0,0 +1,16 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class DynamicExceptionSpec : ParentNode
+ {
+ public DynamicExceptionSpec(BaseNode child) : base(NodeType.DynamicExceptionSpec, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("throw(");
+ Child.Print(writer);
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs
new file mode 100644
index 00000000..086cd3dc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs
@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ElaboratedType : ParentNode
+ {
+ private string _elaborated;
+
+ public ElaboratedType(string elaborated, BaseNode type) : base(NodeType.ElaboratedType, type)
+ {
+ _elaborated = elaborated;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_elaborated);
+ writer.Write(" ");
+ Child.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs
new file mode 100644
index 00000000..b45481dd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs
@@ -0,0 +1,25 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class EnclosedExpression : BaseNode
+ {
+ private string _prefix;
+ private BaseNode _expression;
+ private string _postfix;
+
+ public EnclosedExpression(string prefix, BaseNode expression, string postfix) : base(NodeType.EnclosedExpression)
+ {
+ _prefix = prefix;
+ _expression = expression;
+ _postfix = postfix;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_prefix);
+ _expression.Print(writer);
+ writer.Write(_postfix);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs
new file mode 100644
index 00000000..c7b6dab1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs
@@ -0,0 +1,77 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class EncodedFunction : BaseNode
+ {
+ private BaseNode _name;
+ private BaseNode _params;
+ private BaseNode _cv;
+ private BaseNode _ref;
+ private BaseNode _attrs;
+ private BaseNode _ret;
+
+ public EncodedFunction(BaseNode name, BaseNode Params, BaseNode cv, BaseNode Ref, BaseNode attrs, BaseNode ret) : base(NodeType.NameType)
+ {
+ _name = name;
+ _params = Params;
+ _cv = cv;
+ _ref = Ref;
+ _attrs = attrs;
+ _ret = ret;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_ret != null)
+ {
+ _ret.PrintLeft(writer);
+
+ if (!_ret.HasRightPart())
+ {
+ writer.Write(" ");
+ }
+ }
+
+ _name.Print(writer);
+
+ }
+
+ public override bool HasRightPart()
+ {
+ return true;
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ writer.Write("(");
+
+ if (_params != null)
+ {
+ _params.Print(writer);
+ }
+
+ writer.Write(")");
+
+ if (_ret != null)
+ {
+ _ret.PrintRight(writer);
+ }
+
+ if (_cv != null)
+ {
+ _cv.Print(writer);
+ }
+
+ if (_ref != null)
+ {
+ _ref.Print(writer);
+ }
+
+ if (_attrs != null)
+ {
+ _attrs.Print(writer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs
new file mode 100644
index 00000000..04f7053e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs
@@ -0,0 +1,48 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class FoldExpression : BaseNode
+ {
+ private bool _isLeftFold;
+ private string _operatorName;
+ private BaseNode _expression;
+ private BaseNode _initializer;
+
+ public FoldExpression(bool isLeftFold, string operatorName, BaseNode expression, BaseNode initializer) : base(NodeType.FunctionParameter)
+ {
+ _isLeftFold = isLeftFold;
+ _operatorName = operatorName;
+ _expression = expression;
+ _initializer = initializer;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+
+ if (_isLeftFold && _initializer != null)
+ {
+ _initializer.Print(writer);
+ writer.Write(" ");
+ writer.Write(_operatorName);
+ writer.Write(" ");
+ }
+
+ writer.Write(_isLeftFold ? "... " : " ");
+ writer.Write(_operatorName);
+ writer.Write(!_isLeftFold ? " ..." : " ");
+ _expression.Print(writer);
+
+ if (!_isLeftFold && _initializer != null)
+ {
+ _initializer.Print(writer);
+ writer.Write(" ");
+ writer.Write(_operatorName);
+ writer.Write(" ");
+ }
+
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs
new file mode 100644
index 00000000..1bbf6ef9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs
@@ -0,0 +1,36 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ForwardTemplateReference : BaseNode
+ {
+ // TODO: Compute inside the Demangler
+ public BaseNode Reference;
+ private int _index;
+
+ public ForwardTemplateReference(int index) : base(NodeType.ForwardTemplateReference)
+ {
+ _index = index;
+ }
+
+ public override string GetName()
+ {
+ return Reference.GetName();
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ Reference.PrintLeft(writer);
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ Reference.PrintRight(writer);
+ }
+
+ public override bool HasRightPart()
+ {
+ return Reference.HasRightPart();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs
new file mode 100644
index 00000000..5654a048
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class FunctionParameter : BaseNode
+ {
+ private string _number;
+
+ public FunctionParameter(string number) : base(NodeType.FunctionParameter)
+ {
+ _number = number;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("fp ");
+
+ if (_number != null)
+ {
+ writer.Write(_number);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs
new file mode 100644
index 00000000..4ad0c9f5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs
@@ -0,0 +1,61 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class FunctionType : BaseNode
+ {
+ private BaseNode _returnType;
+ private BaseNode _params;
+ private BaseNode _cvQualifier;
+ private SimpleReferenceType _referenceQualifier;
+ private BaseNode _exceptionSpec;
+
+ public FunctionType(BaseNode returnType, BaseNode Params, BaseNode cvQualifier, SimpleReferenceType referenceQualifier, BaseNode exceptionSpec) : base(NodeType.FunctionType)
+ {
+ _returnType = returnType;
+ _params = Params;
+ _cvQualifier = cvQualifier;
+ _referenceQualifier = referenceQualifier;
+ _exceptionSpec = exceptionSpec;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _returnType.PrintLeft(writer);
+ writer.Write(" ");
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ writer.Write("(");
+ _params.Print(writer);
+ writer.Write(")");
+
+ _returnType.PrintRight(writer);
+
+ _cvQualifier.Print(writer);
+
+ if (_referenceQualifier.Qualifier != Reference.None)
+ {
+ writer.Write(" ");
+ _referenceQualifier.PrintQualifier(writer);
+ }
+
+ if (_exceptionSpec != null)
+ {
+ writer.Write(" ");
+ _exceptionSpec.Print(writer);
+ }
+ }
+
+ public override bool HasRightPart()
+ {
+ return true;
+ }
+
+ public override bool HasFunctions()
+ {
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs
new file mode 100644
index 00000000..d3b6a558
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs
@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class GlobalQualifiedName : ParentNode
+ {
+ public GlobalQualifiedName(BaseNode child) : base(NodeType.GlobalQualifiedName, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("::");
+ Child.Print(writer);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs
new file mode 100644
index 00000000..7155dd60
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class InitListExpression : BaseNode
+ {
+ private BaseNode _typeNode;
+ private List<BaseNode> _nodes;
+
+ public InitListExpression(BaseNode typeNode, List<BaseNode> nodes) : base(NodeType.InitListExpression)
+ {
+ _typeNode = typeNode;
+ _nodes = nodes;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_typeNode != null)
+ {
+ _typeNode.Print(writer);
+ }
+
+ writer.Write("{");
+ writer.Write(string.Join<BaseNode>(", ", _nodes.ToArray()));
+ writer.Write("}");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs
new file mode 100644
index 00000000..ef07414d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs
@@ -0,0 +1,22 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class IntegerCastExpression : ParentNode
+ {
+ private string _number;
+
+ public IntegerCastExpression(BaseNode type, string number) : base(NodeType.IntegerCastExpression, type)
+ {
+ _number = number;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+ Child.Print(writer);
+ writer.Write(")");
+ writer.Write(_number);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs
new file mode 100644
index 00000000..33752d00
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs
@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class IntegerLiteral : BaseNode
+ {
+ private string _literalName;
+ private string _literalValue;
+
+ public IntegerLiteral(string literalName, string literalValue) : base(NodeType.IntegerLiteral)
+ {
+ _literalValue = literalValue;
+ _literalName = literalName;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_literalName.Length > 3)
+ {
+ writer.Write("(");
+ writer.Write(_literalName);
+ writer.Write(")");
+ }
+
+ if (_literalValue[0] == 'n')
+ {
+ writer.Write("-");
+ writer.Write(_literalValue.AsSpan(1));
+ }
+ else
+ {
+ writer.Write(_literalValue);
+ }
+
+ if (_literalName.Length <= 3)
+ {
+ writer.Write(_literalName);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs
new file mode 100644
index 00000000..f7e86c9e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs
@@ -0,0 +1,16 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class LiteralOperator : ParentNode
+ {
+ public LiteralOperator(BaseNode child) : base(NodeType.LiteralOperator, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("operator \"");
+ Child.PrintLeft(writer);
+ writer.Write("\"");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs
new file mode 100644
index 00000000..15d46b38
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs
@@ -0,0 +1,23 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class LocalName : BaseNode
+ {
+ private BaseNode _encoding;
+ private BaseNode _entity;
+
+ public LocalName(BaseNode encoding, BaseNode entity) : base(NodeType.LocalName)
+ {
+ _encoding = encoding;
+ _entity = entity;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _encoding.Print(writer);
+ writer.Write("::");
+ _entity.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs
new file mode 100644
index 00000000..9b91f6f5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs
@@ -0,0 +1,25 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class MemberExpression : BaseNode
+ {
+ private BaseNode _leftNode;
+ private string _kind;
+ private BaseNode _rightNode;
+
+ public MemberExpression(BaseNode leftNode, string kind, BaseNode rightNode) : base(NodeType.MemberExpression)
+ {
+ _leftNode = leftNode;
+ _kind = kind;
+ _rightNode = rightNode;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _leftNode.Print(writer);
+ writer.Write(_kind);
+ _rightNode.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs
new file mode 100644
index 00000000..f9f4cb20
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs
@@ -0,0 +1,29 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NameType : BaseNode
+ {
+ private string _nameValue;
+
+ public NameType(string nameValue, NodeType type) : base(type)
+ {
+ _nameValue = nameValue;
+ }
+
+ public NameType(string nameValue) : base(NodeType.NameType)
+ {
+ _nameValue = nameValue;
+ }
+
+ public override string GetName()
+ {
+ return _nameValue;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_nameValue);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs
new file mode 100644
index 00000000..ee725f36
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NameTypeWithTemplateArguments : BaseNode
+ {
+ private BaseNode _prev;
+ private BaseNode _templateArgument;
+
+ public NameTypeWithTemplateArguments(BaseNode prev, BaseNode templateArgument) : base(NodeType.NameTypeWithTemplateArguments)
+ {
+ _prev = prev;
+ _templateArgument = templateArgument;
+ }
+
+ public override string GetName()
+ {
+ return _prev.GetName();
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _prev.Print(writer);
+ _templateArgument.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs
new file mode 100644
index 00000000..640c200c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs
@@ -0,0 +1,26 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NestedName : ParentNode
+ {
+ private BaseNode _name;
+
+ public NestedName(BaseNode name, BaseNode type) : base(NodeType.NestedName, type)
+ {
+ _name = name;
+ }
+
+ public override string GetName()
+ {
+ return _name.GetName();
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ Child.Print(writer);
+ writer.Write("::");
+ _name.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs
new file mode 100644
index 00000000..ba4690af
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs
@@ -0,0 +1,55 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NewExpression : BaseNode
+ {
+ private NodeArray _expressions;
+ private BaseNode _typeNode;
+ private NodeArray _initializers;
+
+ private bool _isGlobal;
+ private bool _isArrayExpression;
+
+ public NewExpression(NodeArray expressions, BaseNode typeNode, NodeArray initializers, bool isGlobal, bool isArrayExpression) : base(NodeType.NewExpression)
+ {
+ _expressions = expressions;
+ _typeNode = typeNode;
+ _initializers = initializers;
+
+ _isGlobal = isGlobal;
+ _isArrayExpression = isArrayExpression;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (_isGlobal)
+ {
+ writer.Write("::operator ");
+ }
+
+ writer.Write("new ");
+
+ if (_isArrayExpression)
+ {
+ writer.Write("[] ");
+ }
+
+ if (_expressions.Nodes.Count != 0)
+ {
+ writer.Write("(");
+ _expressions.Print(writer);
+ writer.Write(")");
+ }
+
+ _typeNode.Print(writer);
+
+ if (_initializers.Nodes.Count != 0)
+ {
+ writer.Write("(");
+ _initializers.Print(writer);
+ writer.Write(")");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs
new file mode 100644
index 00000000..1482dfc3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NodeArray : BaseNode
+ {
+ public List<BaseNode> Nodes { get; protected set; }
+
+ public NodeArray(List<BaseNode> nodes) : base(NodeType.NodeArray)
+ {
+ Nodes = nodes;
+ }
+
+ public NodeArray(List<BaseNode> nodes, NodeType type) : base(type)
+ {
+ Nodes = nodes;
+ }
+
+ public override bool IsArray()
+ {
+ return true;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(string.Join<BaseNode>(", ", Nodes.ToArray()));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs
new file mode 100644
index 00000000..49044493
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs
@@ -0,0 +1,16 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class NoexceptSpec : ParentNode
+ {
+ public NoexceptSpec(BaseNode child) : base(NodeType.NoexceptSpec, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("noexcept(");
+ Child.Print(writer);
+ writer.Write(")");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs
new file mode 100644
index 00000000..4c820095
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PackedTemplateParameter : NodeArray
+ {
+ public PackedTemplateParameter(List<BaseNode> nodes) : base(nodes, NodeType.PackedTemplateParameter) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ foreach (BaseNode node in Nodes)
+ {
+ node.PrintLeft(writer);
+ }
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ foreach (BaseNode node in Nodes)
+ {
+ node.PrintLeft(writer);
+ }
+ }
+
+ public override bool HasRightPart()
+ {
+ foreach (BaseNode node in Nodes)
+ {
+ if (node.HasRightPart())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs
new file mode 100644
index 00000000..c3645044
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PackedTemplateParameterExpansion : ParentNode
+ {
+ public PackedTemplateParameterExpansion(BaseNode child) : base(NodeType.PackedTemplateParameterExpansion, child) {}
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (Child is PackedTemplateParameter)
+ {
+ if (((PackedTemplateParameter)Child).Nodes.Count != 0)
+ {
+ Child.Print(writer);
+ }
+ }
+ else
+ {
+ writer.Write("...");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs
new file mode 100644
index 00000000..786abced
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public abstract class ParentNode : BaseNode
+ {
+ public BaseNode Child { get; private set; }
+
+ public ParentNode(NodeType type, BaseNode child) : base(type)
+ {
+ Child = child;
+ }
+
+ public override string GetName()
+ {
+ return Child.GetName();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs
new file mode 100644
index 00000000..b1a3ec42
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs
@@ -0,0 +1,45 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PointerType : BaseNode
+ {
+ private BaseNode _child;
+
+ public PointerType(BaseNode child) : base(NodeType.PointerType)
+ {
+ _child = child;
+ }
+
+ public override bool HasRightPart()
+ {
+ return _child.HasRightPart();
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _child.PrintLeft(writer);
+ if (_child.IsArray())
+ {
+ writer.Write(" ");
+ }
+
+ if (_child.IsArray() || _child.HasFunctions())
+ {
+ writer.Write("(");
+ }
+
+ writer.Write("*");
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ if (_child.IsArray() || _child.HasFunctions())
+ {
+ writer.Write(")");
+ }
+
+ _child.PrintRight(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs
new file mode 100644
index 00000000..ccaea3ba
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs
@@ -0,0 +1,22 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PostfixExpression : ParentNode
+ {
+ private string _operator;
+
+ public PostfixExpression(BaseNode type, string Operator) : base(NodeType.PostfixExpression, type)
+ {
+ _operator = Operator;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("(");
+ Child.Print(writer);
+ writer.Write(")");
+ writer.Write(_operator);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs
new file mode 100644
index 00000000..5024a8f9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs
@@ -0,0 +1,20 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PostfixQualifiedType : ParentNode
+ {
+ private string _postfixQualifier;
+
+ public PostfixQualifiedType(string postfixQualifier, BaseNode type) : base(NodeType.PostfixQualifiedType, type)
+ {
+ _postfixQualifier = postfixQualifier;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ Child.Print(writer);
+ writer.Write(_postfixQualifier);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs
new file mode 100644
index 00000000..9c3d4552
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs
@@ -0,0 +1,22 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class PrefixExpression : ParentNode
+ {
+ private string _prefix;
+
+ public PrefixExpression(string prefix, BaseNode child) : base(NodeType.PrefixExpression, child)
+ {
+ _prefix = prefix;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_prefix);
+ writer.Write("(");
+ Child.Print(writer);
+ writer.Write(")");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs
new file mode 100644
index 00000000..2e18f564
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs
@@ -0,0 +1,23 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class QualifiedName : BaseNode
+ {
+ private BaseNode _qualifier;
+ private BaseNode _name;
+
+ public QualifiedName(BaseNode qualifier, BaseNode name) : base(NodeType.QualifiedName)
+ {
+ _qualifier = qualifier;
+ _name = name;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _qualifier.Print(writer);
+ writer.Write("::");
+ _name.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs
new file mode 100644
index 00000000..cb6dd6bf
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs
@@ -0,0 +1,120 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public enum Cv
+ {
+ None,
+ Const,
+ Volatile,
+ Restricted = 4
+ }
+
+ public enum Reference
+ {
+ None,
+ RValue,
+ LValue
+ }
+
+ public class CvType : ParentNode
+ {
+ public Cv Qualifier;
+
+ public CvType(Cv qualifier, BaseNode child) : base(NodeType.CvQualifierType, child)
+ {
+ Qualifier = qualifier;
+ }
+
+ public void PrintQualifier(TextWriter writer)
+ {
+ if ((Qualifier & Cv.Const) != 0)
+ {
+ writer.Write(" const");
+ }
+
+ if ((Qualifier & Cv.Volatile) != 0)
+ {
+ writer.Write(" volatile");
+ }
+
+ if ((Qualifier & Cv.Restricted) != 0)
+ {
+ writer.Write(" restrict");
+ }
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (Child != null)
+ {
+ Child.PrintLeft(writer);
+ }
+
+ PrintQualifier(writer);
+ }
+
+ public override bool HasRightPart()
+ {
+ return Child != null && Child.HasRightPart();
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ if (Child != null)
+ {
+ Child.PrintRight(writer);
+ }
+ }
+ }
+
+ public class SimpleReferenceType : ParentNode
+ {
+ public Reference Qualifier;
+
+ public SimpleReferenceType(Reference qualifier, BaseNode child) : base(NodeType.SimpleReferenceType, child)
+ {
+ Qualifier = qualifier;
+ }
+
+ public void PrintQualifier(TextWriter writer)
+ {
+ if ((Qualifier & Reference.LValue) != 0)
+ {
+ writer.Write("&");
+ }
+
+ if ((Qualifier & Reference.RValue) != 0)
+ {
+ writer.Write("&&");
+ }
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (Child != null)
+ {
+ Child.PrintLeft(writer);
+ }
+ else if (Qualifier != Reference.None)
+ {
+ writer.Write(" ");
+ }
+
+ PrintQualifier(writer);
+ }
+
+ public override bool HasRightPart()
+ {
+ return Child != null && Child.HasRightPart();
+ }
+
+ public override void PrintRight(TextWriter writer)
+ {
+ if (Child != null)
+ {
+ Child.PrintRight(writer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs
new file mode 100644
index 00000000..a3214171
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs
@@ -0,0 +1,47 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ReferenceType : BaseNode
+ {
+ private string _reference;
+ private BaseNode _child;
+
+ public ReferenceType(string reference, BaseNode child) : base(NodeType.ReferenceType)
+ {
+ _reference = reference;
+ _child = child;
+ }
+
+ public override bool HasRightPart()
+ {
+ return _child.HasRightPart();
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ _child.PrintLeft(writer);
+
+ if (_child.IsArray())
+ {
+ writer.Write(" ");
+ }
+
+ if (_child.IsArray() || _child.HasFunctions())
+ {
+ writer.Write("(");
+ }
+
+ writer.Write(_reference);
+ }
+ public override void PrintRight(TextWriter writer)
+ {
+ if (_child.IsArray() || _child.HasFunctions())
+ {
+ writer.Write(")");
+ }
+
+ _child.PrintRight(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs
new file mode 100644
index 00000000..1447458b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs
@@ -0,0 +1,20 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class SpecialName : ParentNode
+ {
+ private string _specialValue;
+
+ public SpecialName(string specialValue, BaseNode type) : base(NodeType.SpecialName, type)
+ {
+ _specialValue = specialValue;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write(_specialValue);
+ Child.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs
new file mode 100644
index 00000000..8d45e180
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs
@@ -0,0 +1,89 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class SpecialSubstitution : BaseNode
+ {
+ public enum SpecialType
+ {
+ Allocator,
+ BasicString,
+ String,
+ IStream,
+ OStream,
+ IOStream
+ }
+
+ private SpecialType _specialSubstitutionKey;
+
+ public SpecialSubstitution(SpecialType specialSubstitutionKey) : base(NodeType.SpecialSubstitution)
+ {
+ _specialSubstitutionKey = specialSubstitutionKey;
+ }
+
+ public void SetExtended()
+ {
+ Type = NodeType.ExpandedSpecialSubstitution;
+ }
+
+ public override string GetName()
+ {
+ switch (_specialSubstitutionKey)
+ {
+ case SpecialType.Allocator:
+ return "allocator";
+ case SpecialType.BasicString:
+ return "basic_string";
+ case SpecialType.String:
+ if (Type == NodeType.ExpandedSpecialSubstitution)
+ {
+ return "basic_string";
+ }
+
+ return "string";
+ case SpecialType.IStream:
+ return "istream";
+ case SpecialType.OStream:
+ return "ostream";
+ case SpecialType.IOStream:
+ return "iostream";
+ }
+
+ return null;
+ }
+
+ private string GetExtendedName()
+ {
+ switch (_specialSubstitutionKey)
+ {
+ case SpecialType.Allocator:
+ return "std::allocator";
+ case SpecialType.BasicString:
+ return "std::basic_string";
+ case SpecialType.String:
+ return "std::basic_string<char, std::char_traits<char>, std::allocator<char> >";
+ case SpecialType.IStream:
+ return "std::basic_istream<char, std::char_traits<char> >";
+ case SpecialType.OStream:
+ return "std::basic_ostream<char, std::char_traits<char> >";
+ case SpecialType.IOStream:
+ return "std::basic_iostream<char, std::char_traits<char> >";
+ }
+
+ return null;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ if (Type == NodeType.ExpandedSpecialSubstitution)
+ {
+ writer.Write(GetExtendedName());
+ }
+ else
+ {
+ writer.Write("std::");
+ writer.Write(GetName());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs
new file mode 100644
index 00000000..c3a97d60
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs
@@ -0,0 +1,15 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class StdQualifiedName : ParentNode
+ {
+ public StdQualifiedName(BaseNode child) : base(NodeType.StdQualifiedName, child) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("std::");
+ Child.Print(writer);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs
new file mode 100644
index 00000000..aefd668d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class TemplateArguments : NodeArray
+ {
+ public TemplateArguments(List<BaseNode> nodes) : base(nodes, NodeType.TemplateArguments) { }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ string Params = string.Join<BaseNode>(", ", Nodes.ToArray());
+
+ writer.Write("<");
+
+ writer.Write(Params);
+
+ if (Params.EndsWith(">"))
+ {
+ writer.Write(" ");
+ }
+
+ writer.Write(">");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs
new file mode 100644
index 00000000..2972a31c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs
@@ -0,0 +1,20 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast
+{
+ public class ThrowExpression : BaseNode
+ {
+ private BaseNode _expression;
+
+ public ThrowExpression(BaseNode expression) : base(NodeType.ThrowExpression)
+ {
+ _expression = expression;
+ }
+
+ public override void PrintLeft(TextWriter writer)
+ {
+ writer.Write("throw ");
+ _expression.Print(writer);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs
new file mode 100644
index 00000000..1bfd7ac0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs
@@ -0,0 +1,3367 @@
+using Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
+{
+ class Demangler
+ {
+ private static readonly string Base36 = "0123456789abcdefghijklmnopqrstuvwxyz";
+ private List<BaseNode> _substitutionList = new List<BaseNode>();
+ private List<BaseNode> _templateParamList = new List<BaseNode>();
+
+ private List<ForwardTemplateReference> _forwardTemplateReferenceList = new List<ForwardTemplateReference>();
+
+ public string Mangled { get; private set; }
+
+ private int _position;
+ private int _length;
+
+ private bool _canForwardTemplateReference;
+ private bool _canParseTemplateArgs;
+
+ public Demangler(string mangled)
+ {
+ Mangled = mangled;
+ _position = 0;
+ _length = mangled.Length;
+ _canParseTemplateArgs = true;
+ }
+
+ private bool ConsumeIf(string toConsume)
+ {
+ var mangledPart = Mangled.AsSpan(_position);
+
+ if (mangledPart.StartsWith(toConsume.AsSpan()))
+ {
+ _position += toConsume.Length;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private ReadOnlySpan<char> PeekString(int offset = 0, int length = 1)
+ {
+ if (_position + offset >= length)
+ {
+ return null;
+ }
+
+ return Mangled.AsSpan(_position + offset, length);
+ }
+
+ private char Peek(int offset = 0)
+ {
+ if (_position + offset >= _length)
+ {
+ return '\0';
+ }
+
+ return Mangled[_position + offset];
+ }
+
+ private char Consume()
+ {
+ if (_position < _length)
+ {
+ return Mangled[_position++];
+ }
+
+ return '\0';
+ }
+
+ private int Count()
+ {
+ return _length - _position;
+ }
+
+ private static int FromBase36(string encoded)
+ {
+ char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray();
+
+ int result = 0;
+
+ for (int i = 0; i < reversedEncoded.Length; i++)
+ {
+ int value = Base36.IndexOf(reversedEncoded[i]);
+ if (value == -1)
+ {
+ return -1;
+ }
+
+ result += value * (int)Math.Pow(36, i);
+ }
+
+ return result;
+ }
+
+ private int ParseSeqId()
+ {
+ ReadOnlySpan<char> part = Mangled.AsSpan(_position);
+ int seqIdLen = 0;
+
+ for (; seqIdLen < part.Length; seqIdLen++)
+ {
+ if (!char.IsLetterOrDigit(part[seqIdLen]))
+ {
+ break;
+ }
+ }
+
+ _position += seqIdLen;
+
+ return FromBase36(new string(part[..seqIdLen]));
+ }
+
+ // <substitution> ::= S <seq-id> _
+ // ::= S_
+ // ::= St # std::
+ // ::= Sa # std::allocator
+ // ::= Sb # std::basic_string
+ // ::= Ss # std::basic_string<char, std::char_traits<char>, std::allocator<char> >
+ // ::= Si # std::basic_istream<char, std::char_traits<char> >
+ // ::= So # std::basic_ostream<char, std::char_traits<char> >
+ // ::= Sd # std::basic_iostream<char, std::char_traits<char> >
+ private BaseNode ParseSubstitution()
+ {
+ if (!ConsumeIf("S"))
+ {
+ return null;
+ }
+
+ char substitutionSecondChar = Peek();
+ if (char.IsLower(substitutionSecondChar))
+ {
+ switch (substitutionSecondChar)
+ {
+ case 'a':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.Allocator);
+ case 'b':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.BasicString);
+ case 's':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.String);
+ case 'i':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.IStream);
+ case 'o':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.OStream);
+ case 'd':
+ _position++;
+ return new SpecialSubstitution(SpecialSubstitution.SpecialType.IOStream);
+ default:
+ return null;
+ }
+ }
+
+ // ::= S_
+ if (ConsumeIf("_"))
+ {
+ if (_substitutionList.Count != 0)
+ {
+ return _substitutionList[0];
+ }
+
+ return null;
+ }
+
+ // ::= S <seq-id> _
+ int seqId = ParseSeqId();
+ if (seqId < 0)
+ {
+ return null;
+ }
+
+ seqId++;
+
+ if (!ConsumeIf("_") || seqId >= _substitutionList.Count)
+ {
+ return null;
+ }
+
+ return _substitutionList[seqId];
+ }
+
+ // NOTE: thoses data aren't used in the output
+ // <call-offset> ::= h <nv-offset> _
+ // ::= v <v-offset> _
+ // <nv-offset> ::= <offset number>
+ // # non-virtual base override
+ // <v-offset> ::= <offset number> _ <virtual offset number>
+ // # virtual base override, with vcall offset
+ private bool ParseCallOffset()
+ {
+ if (ConsumeIf("h"))
+ {
+ return ParseNumber(true).Length == 0 || !ConsumeIf("_");
+ }
+ else if (ConsumeIf("v"))
+ {
+ return ParseNumber(true).Length == 0 || !ConsumeIf("_") || ParseNumber(true).Length == 0 || !ConsumeIf("_");
+ }
+
+ return true;
+ }
+
+
+ // <class-enum-type> ::= <name> # non-dependent type name, dependent type name, or dependent typename-specifier
+ // ::= Ts <name> # dependent elaborated type specifier using 'struct' or 'class'
+ // ::= Tu <name> # dependent elaborated type specifier using 'union'
+ // ::= Te <name> # dependent elaborated type specifier using 'enum'
+ private BaseNode ParseClassEnumType()
+ {
+ string elaboratedType = null;
+
+ if (ConsumeIf("Ts"))
+ {
+ elaboratedType = "struct";
+ }
+ else if (ConsumeIf("Tu"))
+ {
+ elaboratedType = "union";
+ }
+ else if (ConsumeIf("Te"))
+ {
+ elaboratedType = "enum";
+ }
+
+ BaseNode name = ParseName();
+ if (name == null)
+ {
+ return null;
+ }
+
+ if (elaboratedType == null)
+ {
+ return name;
+ }
+
+ return new ElaboratedType(elaboratedType, name);
+ }
+
+ // <function-type> ::= [<CV-qualifiers>] [<exception-spec>] [Dx] F [Y] <bare-function-type> [<ref-qualifier>] E
+ // <bare-function-type> ::= <signature type>+
+ // # types are possible return type, then parameter types
+ // <exception-spec> ::= Do # non-throwing exception-specification (e.g., noexcept, throw())
+ // ::= DO <expression> E # computed (instantiation-dependent) noexcept
+ // ::= Dw <type>+ E # dynamic exception specification with instantiation-dependent types
+ private BaseNode ParseFunctionType()
+ {
+ Cv cvQualifiers = ParseCvQualifiers();
+
+ BaseNode exceptionSpec = null;
+
+ if (ConsumeIf("Do"))
+ {
+ exceptionSpec = new NameType("noexcept");
+ }
+ else if (ConsumeIf("DO"))
+ {
+ BaseNode expression = ParseExpression();
+ if (expression == null || !ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ exceptionSpec = new NoexceptSpec(expression);
+ }
+ else if (ConsumeIf("Dw"))
+ {
+ List<BaseNode> types = new List<BaseNode>();
+
+ while (!ConsumeIf("E"))
+ {
+ BaseNode type = ParseType();
+ if (type == null)
+ {
+ return null;
+ }
+
+ types.Add(type);
+ }
+
+ exceptionSpec = new DynamicExceptionSpec(new NodeArray(types));
+ }
+
+ // We don't need the transaction
+ ConsumeIf("Dx");
+
+ if (!ConsumeIf("F"))
+ {
+ return null;
+ }
+
+ // extern "C"
+ ConsumeIf("Y");
+
+ BaseNode returnType = ParseType();
+ if (returnType == null)
+ {
+ return null;
+ }
+
+ Reference referenceQualifier = Reference.None;
+ List<BaseNode> Params = new List<BaseNode>();
+
+ while (true)
+ {
+ if (ConsumeIf("E"))
+ {
+ break;
+ }
+
+ if (ConsumeIf("v"))
+ {
+ continue;
+ }
+
+ if (ConsumeIf("RE"))
+ {
+ referenceQualifier = Reference.LValue;
+ break;
+ }
+ else if (ConsumeIf("OE"))
+ {
+ referenceQualifier = Reference.RValue;
+ break;
+ }
+
+ BaseNode type = ParseType();
+ if (type == null)
+ {
+ return null;
+ }
+
+ Params.Add(type);
+ }
+
+ return new FunctionType(returnType, new NodeArray(Params), new CvType(cvQualifiers, null), new SimpleReferenceType(referenceQualifier, null), exceptionSpec);
+ }
+
+ // <array-type> ::= A <positive dimension number> _ <element type>
+ // ::= A [<dimension expression>] _ <element type>
+ private BaseNode ParseArrayType()
+ {
+ if (!ConsumeIf("A"))
+ {
+ return null;
+ }
+
+ BaseNode elementType;
+ if (char.IsDigit(Peek()))
+ {
+ string dimension = ParseNumber();
+ if (dimension.Length == 0 || !ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ elementType = ParseType();
+ if (elementType == null)
+ {
+ return null;
+ }
+
+ return new ArrayType(elementType, dimension);
+ }
+
+ if (!ConsumeIf("_"))
+ {
+ BaseNode dimensionExpression = ParseExpression();
+ if (dimensionExpression == null || !ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ elementType = ParseType();
+ if (elementType == null)
+ {
+ return null;
+ }
+
+ return new ArrayType(elementType, dimensionExpression);
+ }
+
+ elementType = ParseType();
+ if (elementType == null)
+ {
+ return null;
+ }
+
+ return new ArrayType(elementType);
+ }
+
+ // <type> ::= <builtin-type>
+ // ::= <qualified-type> (PARTIAL)
+ // ::= <function-type>
+ // ::= <class-enum-type>
+ // ::= <array-type> (TODO)
+ // ::= <pointer-to-member-type> (TODO)
+ // ::= <template-param>
+ // ::= <template-template-param> <template-args>
+ // ::= <decltype>
+ // ::= P <type> # pointer
+ // ::= R <type> # l-value reference
+ // ::= O <type> # r-value reference (C++11)
+ // ::= C <type> # complex pair (C99)
+ // ::= G <type> # imaginary (C99)
+ // ::= <substitution> # See Compression below
+ private BaseNode ParseType(NameParserContext context = null)
+ {
+ // Temporary context
+ if (context == null)
+ {
+ context = new NameParserContext();
+ }
+
+ BaseNode result = null;
+ switch (Peek())
+ {
+ case 'r':
+ case 'V':
+ case 'K':
+ int typePos = 0;
+
+ if (Peek(typePos) == 'r')
+ {
+ typePos++;
+ }
+
+ if (Peek(typePos) == 'V')
+ {
+ typePos++;
+ }
+
+ if (Peek(typePos) == 'K')
+ {
+ typePos++;
+ }
+
+ if (Peek(typePos) == 'F' || (Peek(typePos) == 'D' && (Peek(typePos + 1) == 'o' || Peek(typePos + 1) == 'O' || Peek(typePos + 1) == 'w' || Peek(typePos + 1) == 'x')))
+ {
+ result = ParseFunctionType();
+ break;
+ }
+
+ Cv cv = ParseCvQualifiers();
+
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new CvType(cv, result);
+ break;
+ case 'U':
+ // TODO: <extended-qualifier>
+ return null;
+ case 'v':
+ _position++;
+ return new NameType("void");
+ case 'w':
+ _position++;
+ return new NameType("wchar_t");
+ case 'b':
+ _position++;
+ return new NameType("bool");
+ case 'c':
+ _position++;
+ return new NameType("char");
+ case 'a':
+ _position++;
+ return new NameType("signed char");
+ case 'h':
+ _position++;
+ return new NameType("unsigned char");
+ case 's':
+ _position++;
+ return new NameType("short");
+ case 't':
+ _position++;
+ return new NameType("unsigned short");
+ case 'i':
+ _position++;
+ return new NameType("int");
+ case 'j':
+ _position++;
+ return new NameType("unsigned int");
+ case 'l':
+ _position++;
+ return new NameType("long");
+ case 'm':
+ _position++;
+ return new NameType("unsigned long");
+ case 'x':
+ _position++;
+ return new NameType("long long");
+ case 'y':
+ _position++;
+ return new NameType("unsigned long long");
+ case 'n':
+ _position++;
+ return new NameType("__int128");
+ case 'o':
+ _position++;
+ return new NameType("unsigned __int128");
+ case 'f':
+ _position++;
+ return new NameType("float");
+ case 'd':
+ _position++;
+ return new NameType("double");
+ case 'e':
+ _position++;
+ return new NameType("long double");
+ case 'g':
+ _position++;
+ return new NameType("__float128");
+ case 'z':
+ _position++;
+ return new NameType("...");
+ case 'u':
+ _position++;
+ return ParseSourceName();
+ case 'D':
+ switch (Peek(1))
+ {
+ case 'd':
+ _position += 2;
+ return new NameType("decimal64");
+ case 'e':
+ _position += 2;
+ return new NameType("decimal128");
+ case 'f':
+ _position += 2;
+ return new NameType("decimal32");
+ case 'h':
+ _position += 2;
+ // FIXME: GNU c++flit returns this but that is not what is supposed to be returned.
+ return new NameType("half");
+ // return new NameType("decimal16");
+ case 'i':
+ _position += 2;
+ return new NameType("char32_t");
+ case 's':
+ _position += 2;
+ return new NameType("char16_t");
+ case 'a':
+ _position += 2;
+ return new NameType("decltype(auto)");
+ case 'n':
+ _position += 2;
+ // FIXME: GNU c++flit returns this but that is not what is supposed to be returned.
+ return new NameType("decltype(nullptr)");
+ // return new NameType("std::nullptr_t");
+ case 't':
+ case 'T':
+ _position += 2;
+ result = ParseDecltype();
+ break;
+ case 'o':
+ case 'O':
+ case 'w':
+ case 'x':
+ result = ParseFunctionType();
+ break;
+ default:
+ return null;
+ }
+ break;
+ case 'F':
+ result = ParseFunctionType();
+ break;
+ case 'A':
+ return ParseArrayType();
+ case 'M':
+ // TODO: <pointer-to-member-type>
+ _position++;
+ return null;
+ case 'T':
+ // might just be a class enum type
+ if (Peek(1) == 's' || Peek(1) == 'u' || Peek(1) == 'e')
+ {
+ result = ParseClassEnumType();
+ break;
+ }
+
+ result = ParseTemplateParam();
+ if (result == null)
+ {
+ return null;
+ }
+
+ if (_canParseTemplateArgs && Peek() == 'I')
+ {
+ BaseNode templateArguments = ParseTemplateArguments();
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ result = new NameTypeWithTemplateArguments(result, templateArguments);
+ }
+ break;
+ case 'P':
+ _position++;
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new PointerType(result);
+ break;
+ case 'R':
+ _position++;
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new ReferenceType("&", result);
+ break;
+ case 'O':
+ _position++;
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new ReferenceType("&&", result);
+ break;
+ case 'C':
+ _position++;
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new PostfixQualifiedType(" complex", result);
+ break;
+ case 'G':
+ _position++;
+ result = ParseType(context);
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ result = new PostfixQualifiedType(" imaginary", result);
+ break;
+ case 'S':
+ if (Peek(1) != 't')
+ {
+ BaseNode substitution = ParseSubstitution();
+ if (substitution == null)
+ {
+ return null;
+ }
+
+ if (_canParseTemplateArgs && Peek() == 'I')
+ {
+ BaseNode templateArgument = ParseTemplateArgument();
+ if (templateArgument == null)
+ {
+ return null;
+ }
+
+ result = new NameTypeWithTemplateArguments(substitution, templateArgument);
+ break;
+ }
+ return substitution;
+ }
+ else
+ {
+ result = ParseClassEnumType();
+ break;
+ }
+ default:
+ result = ParseClassEnumType();
+ break;
+ }
+ if (result != null)
+ {
+ _substitutionList.Add(result);
+ }
+
+ return result;
+ }
+
+ // <special-name> ::= TV <type> # virtual table
+ // ::= TT <type> # VTT structure (construction vtable index)
+ // ::= TI <type> # typeinfo structure
+ // ::= TS <type> # typeinfo name (null-terminated byte string)
+ // ::= Tc <call-offset> <call-offset> <base encoding>
+ // ::= TW <object name> # Thread-local wrapper
+ // ::= TH <object name> # Thread-local initialization
+ // ::= T <call-offset> <base encoding>
+ // # base is the nominal target function of thunk
+ // ::= GV <object name> # Guard variable for one-time initialization
+ private BaseNode ParseSpecialName(NameParserContext context = null)
+ {
+ if (Peek() != 'T')
+ {
+ if (ConsumeIf("GV"))
+ {
+ BaseNode name = ParseName();
+ if (name == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("guard variable for ", name);
+ }
+ return null;
+ }
+
+ BaseNode node;
+ switch (Peek(1))
+ {
+ // ::= TV <type> # virtual table
+ case 'V':
+ _position += 2;
+ node = ParseType(context);
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("vtable for ", node);
+ // ::= TT <type> # VTT structure (construction vtable index)
+ case 'T':
+ _position += 2;
+ node = ParseType(context);
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("VTT for ", node);
+ // ::= TI <type> # typeinfo structure
+ case 'I':
+ _position += 2;
+ node = ParseType(context);
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("typeinfo for ", node);
+ // ::= TS <type> # typeinfo name (null-terminated byte string)
+ case 'S':
+ _position += 2;
+ node = ParseType(context);
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("typeinfo name for ", node);
+ // ::= Tc <call-offset> <call-offset> <base encoding>
+ case 'c':
+ _position += 2;
+ if (ParseCallOffset() || ParseCallOffset())
+ {
+ return null;
+ }
+
+ node = ParseEncoding();
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("covariant return thunk to ", node);
+ // extension ::= TC <first type> <number> _ <second type>
+ case 'C':
+ _position += 2;
+ BaseNode firstType = ParseType();
+ if (firstType == null || ParseNumber(true).Length == 0 || !ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ BaseNode secondType = ParseType();
+
+ return new CtorVtableSpecialName(secondType, firstType);
+ // ::= TH <object name> # Thread-local initialization
+ case 'H':
+ _position += 2;
+ node = ParseName();
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("thread-local initialization routine for ", node);
+ // ::= TW <object name> # Thread-local wrapper
+ case 'W':
+ _position += 2;
+ node = ParseName();
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new SpecialName("thread-local wrapper routine for ", node);
+ default:
+ _position++;
+ bool isVirtual = Peek() == 'v';
+ if (ParseCallOffset())
+ {
+ return null;
+ }
+
+ node = ParseEncoding();
+ if (node == null)
+ {
+ return null;
+ }
+
+ if (isVirtual)
+ {
+ return new SpecialName("virtual thunk to ", node);
+ }
+
+ return new SpecialName("non-virtual thunk to ", node);
+ }
+ }
+
+ // <CV-qualifiers> ::= [r] [V] [K] # restrict (C99), volatile, const
+ private Cv ParseCvQualifiers()
+ {
+ Cv qualifiers = Cv.None;
+
+ if (ConsumeIf("r"))
+ {
+ qualifiers |= Cv.Restricted;
+ }
+ if (ConsumeIf("V"))
+ {
+ qualifiers |= Cv.Volatile;
+ }
+ if (ConsumeIf("K"))
+ {
+ qualifiers |= Cv.Const;
+ }
+
+ return qualifiers;
+ }
+
+
+ // <ref-qualifier> ::= R # & ref-qualifier
+ // <ref-qualifier> ::= O # && ref-qualifier
+ private SimpleReferenceType ParseRefQualifiers()
+ {
+ Reference result = Reference.None;
+ if (ConsumeIf("O"))
+ {
+ result = Reference.RValue;
+ }
+ else if (ConsumeIf("R"))
+ {
+ result = Reference.LValue;
+ }
+ return new SimpleReferenceType(result, null);
+ }
+
+ private BaseNode CreateNameNode(BaseNode prev, BaseNode name, NameParserContext context)
+ {
+ BaseNode result = name;
+ if (prev != null)
+ {
+ result = new NestedName(name, prev);
+ }
+
+ if (context != null)
+ {
+ context.FinishWithTemplateArguments = false;
+ }
+
+ return result;
+ }
+
+ private int ParsePositiveNumber()
+ {
+ ReadOnlySpan<char> part = Mangled.AsSpan(_position);
+ int numberLength = 0;
+
+ for (; numberLength < part.Length; numberLength++)
+ {
+ if (!char.IsDigit(part[numberLength]))
+ {
+ break;
+ }
+ }
+
+ _position += numberLength;
+
+ if (numberLength == 0)
+ {
+ return -1;
+ }
+
+ return int.Parse(part[..numberLength]);
+ }
+
+ private string ParseNumber(bool isSigned = false)
+ {
+ if (isSigned)
+ {
+ ConsumeIf("n");
+ }
+
+ if (Count() == 0 || !char.IsDigit(Mangled[_position]))
+ {
+ return null;
+ }
+
+ ReadOnlySpan<char> part = Mangled.AsSpan(_position);
+ int numberLength = 0;
+
+ for (; numberLength < part.Length; numberLength++)
+ {
+ if (!char.IsDigit(part[numberLength]))
+ {
+ break;
+ }
+ }
+
+ _position += numberLength;
+
+ return new string(part[..numberLength]);
+ }
+
+ // <source-name> ::= <positive length number> <identifier>
+ private BaseNode ParseSourceName()
+ {
+ int length = ParsePositiveNumber();
+ if (Count() < length || length <= 0)
+ {
+ return null;
+ }
+
+ string name = Mangled.Substring(_position, length);
+ _position += length;
+ if (name.StartsWith("_GLOBAL__N"))
+ {
+ return new NameType("(anonymous namespace)");
+ }
+
+ return new NameType(name);
+ }
+
+ // <operator-name> ::= nw # new
+ // ::= na # new[]
+ // ::= dl # delete
+ // ::= da # delete[]
+ // ::= ps # + (unary)
+ // ::= ng # - (unary)
+ // ::= ad # & (unary)
+ // ::= de # * (unary)
+ // ::= co # ~
+ // ::= pl # +
+ // ::= mi # -
+ // ::= ml # *
+ // ::= dv # /
+ // ::= rm # %
+ // ::= an # &
+ // ::= or # |
+ // ::= eo # ^
+ // ::= aS # =
+ // ::= pL # +=
+ // ::= mI # -=
+ // ::= mL # *=
+ // ::= dV # /=
+ // ::= rM # %=
+ // ::= aN # &=
+ // ::= oR # |=
+ // ::= eO # ^=
+ // ::= ls # <<
+ // ::= rs # >>
+ // ::= lS # <<=
+ // ::= rS # >>=
+ // ::= eq # ==
+ // ::= ne # !=
+ // ::= lt # <
+ // ::= gt # >
+ // ::= le # <=
+ // ::= ge # >=
+ // ::= ss # <=>
+ // ::= nt # !
+ // ::= aa # &&
+ // ::= oo # ||
+ // ::= pp # ++ (postfix in <expression> context)
+ // ::= mm # -- (postfix in <expression> context)
+ // ::= cm # ,
+ // ::= pm # ->*
+ // ::= pt # ->
+ // ::= cl # ()
+ // ::= ix # []
+ // ::= qu # ?
+ // ::= cv <type> # (cast) (TODO)
+ // ::= li <source-name> # operator ""
+ // ::= v <digit> <source-name> # vendor extended operator (TODO)
+ private BaseNode ParseOperatorName(NameParserContext context)
+ {
+ switch (Peek())
+ {
+ case 'a':
+ switch (Peek(1))
+ {
+ case 'a':
+ _position += 2;
+ return new NameType("operator&&");
+ case 'd':
+ case 'n':
+ _position += 2;
+ return new NameType("operator&");
+ case 'N':
+ _position += 2;
+ return new NameType("operator&=");
+ case 'S':
+ _position += 2;
+ return new NameType("operator=");
+ default:
+ return null;
+ }
+ case 'c':
+ switch (Peek(1))
+ {
+ case 'l':
+ _position += 2;
+ return new NameType("operator()");
+ case 'm':
+ _position += 2;
+ return new NameType("operator,");
+ case 'o':
+ _position += 2;
+ return new NameType("operator~");
+ case 'v':
+ _position += 2;
+
+ bool canParseTemplateArgsBackup = _canParseTemplateArgs;
+ bool canForwardTemplateReferenceBackup = _canForwardTemplateReference;
+
+ _canParseTemplateArgs = false;
+ _canForwardTemplateReference = canForwardTemplateReferenceBackup || context != null;
+
+ BaseNode type = ParseType();
+
+ _canParseTemplateArgs = canParseTemplateArgsBackup;
+ _canForwardTemplateReference = canForwardTemplateReferenceBackup;
+
+ if (type == null)
+ {
+ return null;
+ }
+
+ if (context != null)
+ {
+ context.CtorDtorConversion = true;
+ }
+
+ return new ConversionOperatorType(type);
+ default:
+ return null;
+ }
+ case 'd':
+ switch (Peek(1))
+ {
+ case 'a':
+ _position += 2;
+ return new NameType("operator delete[]");
+ case 'e':
+ _position += 2;
+ return new NameType("operator*");
+ case 'l':
+ _position += 2;
+ return new NameType("operator delete");
+ case 'v':
+ _position += 2;
+ return new NameType("operator/");
+ case 'V':
+ _position += 2;
+ return new NameType("operator/=");
+ default:
+ return null;
+ }
+ case 'e':
+ switch (Peek(1))
+ {
+ case 'o':
+ _position += 2;
+ return new NameType("operator^");
+ case 'O':
+ _position += 2;
+ return new NameType("operator^=");
+ case 'q':
+ _position += 2;
+ return new NameType("operator==");
+ default:
+ return null;
+ }
+ case 'g':
+ switch (Peek(1))
+ {
+ case 'e':
+ _position += 2;
+ return new NameType("operator>=");
+ case 't':
+ _position += 2;
+ return new NameType("operator>");
+ default:
+ return null;
+ }
+ case 'i':
+ if (Peek(1) == 'x')
+ {
+ _position += 2;
+ return new NameType("operator[]");
+ }
+ return null;
+ case 'l':
+ switch (Peek(1))
+ {
+ case 'e':
+ _position += 2;
+ return new NameType("operator<=");
+ case 'i':
+ _position += 2;
+ BaseNode sourceName = ParseSourceName();
+ if (sourceName == null)
+ {
+ return null;
+ }
+
+ return new LiteralOperator(sourceName);
+ case 's':
+ _position += 2;
+ return new NameType("operator<<");
+ case 'S':
+ _position += 2;
+ return new NameType("operator<<=");
+ case 't':
+ _position += 2;
+ return new NameType("operator<");
+ default:
+ return null;
+ }
+ case 'm':
+ switch (Peek(1))
+ {
+ case 'i':
+ _position += 2;
+ return new NameType("operator-");
+ case 'I':
+ _position += 2;
+ return new NameType("operator-=");
+ case 'l':
+ _position += 2;
+ return new NameType("operator*");
+ case 'L':
+ _position += 2;
+ return new NameType("operator*=");
+ case 'm':
+ _position += 2;
+ return new NameType("operator--");
+ default:
+ return null;
+ }
+ case 'n':
+ switch (Peek(1))
+ {
+ case 'a':
+ _position += 2;
+ return new NameType("operator new[]");
+ case 'e':
+ _position += 2;
+ return new NameType("operator!=");
+ case 'g':
+ _position += 2;
+ return new NameType("operator-");
+ case 't':
+ _position += 2;
+ return new NameType("operator!");
+ case 'w':
+ _position += 2;
+ return new NameType("operator new");
+ default:
+ return null;
+ }
+ case 'o':
+ switch (Peek(1))
+ {
+ case 'o':
+ _position += 2;
+ return new NameType("operator||");
+ case 'r':
+ _position += 2;
+ return new NameType("operator|");
+ case 'R':
+ _position += 2;
+ return new NameType("operator|=");
+ default:
+ return null;
+ }
+ case 'p':
+ switch (Peek(1))
+ {
+ case 'm':
+ _position += 2;
+ return new NameType("operator->*");
+ case 's':
+ case 'l':
+ _position += 2;
+ return new NameType("operator+");
+ case 'L':
+ _position += 2;
+ return new NameType("operator+=");
+ case 'p':
+ _position += 2;
+ return new NameType("operator++");
+ case 't':
+ _position += 2;
+ return new NameType("operator->");
+ default:
+ return null;
+ }
+ case 'q':
+ if (Peek(1) == 'u')
+ {
+ _position += 2;
+ return new NameType("operator?");
+ }
+ return null;
+ case 'r':
+ switch (Peek(1))
+ {
+ case 'm':
+ _position += 2;
+ return new NameType("operator%");
+ case 'M':
+ _position += 2;
+ return new NameType("operator%=");
+ case 's':
+ _position += 2;
+ return new NameType("operator>>");
+ case 'S':
+ _position += 2;
+ return new NameType("operator>>=");
+ default:
+ return null;
+ }
+ case 's':
+ if (Peek(1) == 's')
+ {
+ _position += 2;
+ return new NameType("operator<=>");
+ }
+ return null;
+ case 'v':
+ // TODO: ::= v <digit> <source-name> # vendor extended operator
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ // <unqualified-name> ::= <operator-name> [<abi-tags> (TODO)]
+ // ::= <ctor-dtor-name> (TODO)
+ // ::= <source-name>
+ // ::= <unnamed-type-name> (TODO)
+ // ::= DC <source-name>+ E # structured binding declaration (TODO)
+ private BaseNode ParseUnqualifiedName(NameParserContext context)
+ {
+ BaseNode result = null;
+ char c = Peek();
+ if (c == 'U')
+ {
+ // TODO: Unnamed Type Name
+ // throw new Exception("Unnamed Type Name not implemented");
+ }
+ else if (char.IsDigit(c))
+ {
+ result = ParseSourceName();
+ }
+ else if (ConsumeIf("DC"))
+ {
+ // TODO: Structured Binding Declaration
+ // throw new Exception("Structured Binding Declaration not implemented");
+ }
+ else
+ {
+ result = ParseOperatorName(context);
+ }
+
+ if (result != null)
+ {
+ // TODO: ABI Tags
+ // throw new Exception("ABI Tags not implemented");
+ }
+ return result;
+ }
+
+ // <ctor-dtor-name> ::= C1 # complete object constructor
+ // ::= C2 # base object constructor
+ // ::= C3 # complete object allocating constructor
+ // ::= D0 # deleting destructor
+ // ::= D1 # complete object destructor
+ // ::= D2 # base object destructor
+ private BaseNode ParseCtorDtorName(NameParserContext context, BaseNode prev)
+ {
+ if (prev.Type == NodeType.SpecialSubstitution && prev is SpecialSubstitution)
+ {
+ ((SpecialSubstitution)prev).SetExtended();
+ }
+
+ if (ConsumeIf("C"))
+ {
+ bool isInherited = ConsumeIf("I");
+
+ char ctorDtorType = Peek();
+ if (ctorDtorType != '1' && ctorDtorType != '2' && ctorDtorType != '3')
+ {
+ return null;
+ }
+
+ _position++;
+
+ if (context != null)
+ {
+ context.CtorDtorConversion = true;
+ }
+
+ if (isInherited && ParseName(context) == null)
+ {
+ return null;
+ }
+
+ return new CtorDtorNameType(prev, false);
+ }
+
+ if (ConsumeIf("D"))
+ {
+ char c = Peek();
+ if (c != '0' && c != '1' && c != '2')
+ {
+ return null;
+ }
+
+ _position++;
+
+ if (context != null)
+ {
+ context.CtorDtorConversion = true;
+ }
+
+ return new CtorDtorNameType(prev, true);
+ }
+
+ return null;
+ }
+
+ // <function-param> ::= fp <top-level CV-qualifiers> _ # L == 0, first parameter
+ // ::= fp <top-level CV-qualifiers> <parameter-2 non-negative number> _ # L == 0, second and later parameters
+ // ::= fL <L-1 non-negative number> p <top-level CV-qualifiers> _ # L > 0, first parameter
+ // ::= fL <L-1 non-negative number> p <top-level CV-qualifiers> <parameter-2 non-negative number> _ # L > 0, second and later parameters
+ private BaseNode ParseFunctionParameter()
+ {
+ if (ConsumeIf("fp"))
+ {
+ // ignored
+ ParseCvQualifiers();
+
+ if (!ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ return new FunctionParameter(ParseNumber());
+ }
+ else if (ConsumeIf("fL"))
+ {
+ string l1Number = ParseNumber();
+ if (l1Number == null || l1Number.Length == 0)
+ {
+ return null;
+ }
+
+ if (!ConsumeIf("p"))
+ {
+ return null;
+ }
+
+ // ignored
+ ParseCvQualifiers();
+
+ if (!ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ return new FunctionParameter(ParseNumber());
+ }
+
+ return null;
+ }
+
+ // <fold-expr> ::= fL <binary-operator-name> <expression> <expression>
+ // ::= fR <binary-operator-name> <expression> <expression>
+ // ::= fl <binary-operator-name> <expression>
+ // ::= fr <binary-operator-name> <expression>
+ private BaseNode ParseFoldExpression()
+ {
+ if (!ConsumeIf("f"))
+ {
+ return null;
+ }
+
+ char foldKind = Peek();
+ bool hasInitializer = foldKind == 'L' || foldKind == 'R';
+ bool isLeftFold = foldKind == 'l' || foldKind == 'L';
+
+ if (!isLeftFold && !(foldKind == 'r' || foldKind == 'R'))
+ {
+ return null;
+ }
+
+ _position++;
+
+ string operatorName = null;
+
+ switch (PeekString(0, 2))
+ {
+ case "aa":
+ operatorName = "&&";
+ break;
+ case "an":
+ operatorName = "&";
+ break;
+ case "aN":
+ operatorName = "&=";
+ break;
+ case "aS":
+ operatorName = "=";
+ break;
+ case "cm":
+ operatorName = ",";
+ break;
+ case "ds":
+ operatorName = ".*";
+ break;
+ case "dv":
+ operatorName = "/";
+ break;
+ case "dV":
+ operatorName = "/=";
+ break;
+ case "eo":
+ operatorName = "^";
+ break;
+ case "eO":
+ operatorName = "^=";
+ break;
+ case "eq":
+ operatorName = "==";
+ break;
+ case "ge":
+ operatorName = ">=";
+ break;
+ case "gt":
+ operatorName = ">";
+ break;
+ case "le":
+ operatorName = "<=";
+ break;
+ case "ls":
+ operatorName = "<<";
+ break;
+ case "lS":
+ operatorName = "<<=";
+ break;
+ case "lt":
+ operatorName = "<";
+ break;
+ case "mi":
+ operatorName = "-";
+ break;
+ case "mI":
+ operatorName = "-=";
+ break;
+ case "ml":
+ operatorName = "*";
+ break;
+ case "mL":
+ operatorName = "*=";
+ break;
+ case "ne":
+ operatorName = "!=";
+ break;
+ case "oo":
+ operatorName = "||";
+ break;
+ case "or":
+ operatorName = "|";
+ break;
+ case "oR":
+ operatorName = "|=";
+ break;
+ case "pl":
+ operatorName = "+";
+ break;
+ case "pL":
+ operatorName = "+=";
+ break;
+ case "rm":
+ operatorName = "%";
+ break;
+ case "rM":
+ operatorName = "%=";
+ break;
+ case "rs":
+ operatorName = ">>";
+ break;
+ case "rS":
+ operatorName = ">>=";
+ break;
+ default:
+ return null;
+ }
+
+ _position += 2;
+
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ BaseNode initializer = null;
+
+ if (hasInitializer)
+ {
+ initializer = ParseExpression();
+ if (initializer == null)
+ {
+ return null;
+ }
+ }
+
+ if (isLeftFold && initializer != null)
+ {
+ BaseNode temp = expression;
+ expression = initializer;
+ initializer = temp;
+ }
+
+ return new FoldExpression(isLeftFold, operatorName, new PackedTemplateParameterExpansion(expression), initializer);
+ }
+
+
+ // ::= cv <type> <expression> # type (expression), conversion with one argument
+ // ::= cv <type> _ <expression>* E # type (expr-list), conversion with other than one argument
+ private BaseNode ParseConversionExpression()
+ {
+ if (!ConsumeIf("cv"))
+ {
+ return null;
+ }
+
+ bool canParseTemplateArgsBackup = _canParseTemplateArgs;
+ _canParseTemplateArgs = false;
+ BaseNode type = ParseType();
+ _canParseTemplateArgs = canParseTemplateArgsBackup;
+
+ if (type == null)
+ {
+ return null;
+ }
+
+ List<BaseNode> expressions = new List<BaseNode>();
+ if (ConsumeIf("_"))
+ {
+ while (!ConsumeIf("E"))
+ {
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ expressions.Add(expression);
+ }
+ }
+ else
+ {
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ expressions.Add(expression);
+ }
+
+ return new ConversionExpression(type, new NodeArray(expressions));
+ }
+
+ private BaseNode ParseBinaryExpression(string name)
+ {
+ BaseNode leftPart = ParseExpression();
+ if (leftPart == null)
+ {
+ return null;
+ }
+
+ BaseNode rightPart = ParseExpression();
+ if (rightPart == null)
+ {
+ return null;
+ }
+
+ return new BinaryExpression(leftPart, name, rightPart);
+ }
+
+ private BaseNode ParsePrefixExpression(string name)
+ {
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new PrefixExpression(name, expression);
+ }
+
+
+ // <braced-expression> ::= <expression>
+ // ::= di <field source-name> <braced-expression> # .name = expr
+ // ::= dx <index expression> <braced-expression> # [expr] = expr
+ // ::= dX <range begin expression> <range end expression> <braced-expression>
+ // # [expr ... expr] = expr
+ private BaseNode ParseBracedExpression()
+ {
+ if (Peek() == 'd')
+ {
+ BaseNode bracedExpressionNode;
+ switch (Peek(1))
+ {
+ case 'i':
+ _position += 2;
+ BaseNode field = ParseSourceName();
+ if (field == null)
+ {
+ return null;
+ }
+
+ bracedExpressionNode = ParseBracedExpression();
+ if (bracedExpressionNode == null)
+ {
+ return null;
+ }
+
+ return new BracedExpression(field, bracedExpressionNode, false);
+ case 'x':
+ _position += 2;
+ BaseNode index = ParseExpression();
+ if (index == null)
+ {
+ return null;
+ }
+
+ bracedExpressionNode = ParseBracedExpression();
+ if (bracedExpressionNode == null)
+ {
+ return null;
+ }
+
+ return new BracedExpression(index, bracedExpressionNode, true);
+ case 'X':
+ _position += 2;
+ BaseNode rangeBeginExpression = ParseExpression();
+ if (rangeBeginExpression == null)
+ {
+ return null;
+ }
+
+ BaseNode rangeEndExpression = ParseExpression();
+ if (rangeEndExpression == null)
+ {
+ return null;
+ }
+
+ bracedExpressionNode = ParseBracedExpression();
+ if (bracedExpressionNode == null)
+ {
+ return null;
+ }
+
+ return new BracedRangeExpression(rangeBeginExpression, rangeEndExpression, bracedExpressionNode);
+ }
+ }
+
+ return ParseExpression();
+ }
+
+ // ::= [gs] nw <expression>* _ <type> E # new (expr-list) type
+ // ::= [gs] nw <expression>* _ <type> <initializer> # new (expr-list) type (init)
+ // ::= [gs] na <expression>* _ <type> E # new[] (expr-list) type
+ // ::= [gs] na <expression>* _ <type> <initializer> # new[] (expr-list) type (init)
+ //
+ // <initializer> ::= pi <expression>* E # parenthesized initialization
+ private BaseNode ParseNewExpression()
+ {
+ bool isGlobal = ConsumeIf("gs");
+ bool isArray = Peek(1) == 'a';
+
+ if (!ConsumeIf("nw") || !ConsumeIf("na"))
+ {
+ return null;
+ }
+
+ List<BaseNode> expressions = new List<BaseNode>();
+ List<BaseNode> initializers = new List<BaseNode>();
+
+ while (!ConsumeIf("_"))
+ {
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ expressions.Add(expression);
+ }
+
+ BaseNode typeNode = ParseType();
+ if (typeNode == null)
+ {
+ return null;
+ }
+
+ if (ConsumeIf("pi"))
+ {
+ while (!ConsumeIf("E"))
+ {
+ BaseNode initializer = ParseExpression();
+ if (initializer == null)
+ {
+ return null;
+ }
+
+ initializers.Add(initializer);
+ }
+ }
+ else if (!ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ return new NewExpression(new NodeArray(expressions), typeNode, new NodeArray(initializers), isGlobal, isArray);
+ }
+
+
+ // <expression> ::= <unary operator-name> <expression>
+ // ::= <binary operator-name> <expression> <expression>
+ // ::= <ternary operator-name> <expression> <expression> <expression>
+ // ::= pp_ <expression> # prefix ++
+ // ::= mm_ <expression> # prefix --
+ // ::= cl <expression>+ E # expression (expr-list), call
+ // ::= cv <type> <expression> # type (expression), conversion with one argument
+ // ::= cv <type> _ <expression>* E # type (expr-list), conversion with other than one argument
+ // ::= tl <type> <braced-expression>* E # type {expr-list}, conversion with braced-init-list argument
+ // ::= il <braced-expression>* E # {expr-list}, braced-init-list in any other context
+ // ::= [gs] nw <expression>* _ <type> E # new (expr-list) type
+ // ::= [gs] nw <expression>* _ <type> <initializer> # new (expr-list) type (init)
+ // ::= [gs] na <expression>* _ <type> E # new[] (expr-list) type
+ // ::= [gs] na <expression>* _ <type> <initializer> # new[] (expr-list) type (init)
+ // ::= [gs] dl <expression> # delete expression
+ // ::= [gs] da <expression> # delete[] expression
+ // ::= dc <type> <expression> # dynamic_cast<type> (expression)
+ // ::= sc <type> <expression> # static_cast<type> (expression)
+ // ::= cc <type> <expression> # const_cast<type> (expression)
+ // ::= rc <type> <expression> # reinterpret_cast<type> (expression)
+ // ::= ti <type> # typeid (type)
+ // ::= te <expression> # typeid (expression)
+ // ::= st <type> # sizeof (type)
+ // ::= sz <expression> # sizeof (expression)
+ // ::= at <type> # alignof (type)
+ // ::= az <expression> # alignof (expression)
+ // ::= nx <expression> # noexcept (expression)
+ // ::= <template-param>
+ // ::= <function-param>
+ // ::= dt <expression> <unresolved-name> # expr.name
+ // ::= pt <expression> <unresolved-name> # expr->name
+ // ::= ds <expression> <expression> # expr.*expr
+ // ::= sZ <template-param> # sizeof...(T), size of a template parameter pack
+ // ::= sZ <function-param> # sizeof...(parameter), size of a function parameter pack
+ // ::= sP <template-arg>* E # sizeof...(T), size of a captured template parameter pack from an alias template
+ // ::= sp <expression> # expression..., pack expansion
+ // ::= tw <expression> # throw expression
+ // ::= tr # throw with no operand (rethrow)
+ // ::= <unresolved-name> # f(p), N::f(p), ::f(p),
+ // # freestanding dependent name (e.g., T::x),
+ // # objectless nonstatic member reference
+ // ::= <expr-primary>
+ private BaseNode ParseExpression()
+ {
+ bool isGlobal = ConsumeIf("gs");
+ BaseNode expression = null;
+ if (Count() < 2)
+ {
+ return null;
+ }
+
+ switch (Peek())
+ {
+ case 'L':
+ return ParseExpressionPrimary();
+ case 'T':
+ return ParseTemplateParam();
+ case 'f':
+ char c = Peek(1);
+ if (c == 'p' || (c == 'L' && char.IsDigit(Peek(2))))
+ {
+ return ParseFunctionParameter();
+ }
+
+ return ParseFoldExpression();
+ case 'a':
+ switch (Peek(1))
+ {
+ case 'a':
+ _position += 2;
+ return ParseBinaryExpression("&&");
+ case 'd':
+ case 'n':
+ _position += 2;
+ return ParseBinaryExpression("&");
+ case 'N':
+ _position += 2;
+ return ParseBinaryExpression("&=");
+ case 'S':
+ _position += 2;
+ return ParseBinaryExpression("=");
+ case 't':
+ _position += 2;
+ BaseNode type = ParseType();
+ if (type == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("alignof (", type, ")");
+ case 'z':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("alignof (", expression, ")");
+ }
+ return null;
+ case 'c':
+ switch (Peek(1))
+ {
+ case 'c':
+ _position += 2;
+ BaseNode to = ParseType();
+ if (to == null)
+ {
+ return null;
+ }
+
+ BaseNode from = ParseExpression();
+ if (from == null)
+ {
+ return null;
+ }
+
+ return new CastExpression("const_cast", to, from);
+ case 'l':
+ _position += 2;
+ BaseNode callee = ParseExpression();
+ if (callee == null)
+ {
+ return null;
+ }
+
+ List<BaseNode> names = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ names.Add(expression);
+ }
+ return new CallExpression(callee, names);
+ case 'm':
+ _position += 2;
+ return ParseBinaryExpression(",");
+ case 'o':
+ _position += 2;
+ return ParsePrefixExpression("~");
+ case 'v':
+ return ParseConversionExpression();
+ }
+ return null;
+ case 'd':
+ BaseNode leftNode = null;
+ BaseNode rightNode = null;
+ switch (Peek(1))
+ {
+ case 'a':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return expression;
+ }
+
+ return new DeleteExpression(expression, isGlobal, true);
+ case 'c':
+ _position += 2;
+ BaseNode type = ParseType();
+ if (type == null)
+ {
+ return null;
+ }
+
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return expression;
+ }
+
+ return new CastExpression("dynamic_cast", type, expression);
+ case 'e':
+ _position += 2;
+ return ParsePrefixExpression("*");
+ case 'l':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new DeleteExpression(expression, isGlobal, false);
+ case 'n':
+ return ParseUnresolvedName();
+ case 's':
+ _position += 2;
+ leftNode = ParseExpression();
+ if (leftNode == null)
+ {
+ return null;
+ }
+
+ rightNode = ParseExpression();
+ if (rightNode == null)
+ {
+ return null;
+ }
+
+ return new MemberExpression(leftNode, ".*", rightNode);
+ case 't':
+ _position += 2;
+ leftNode = ParseExpression();
+ if (leftNode == null)
+ {
+ return null;
+ }
+
+ rightNode = ParseExpression();
+ if (rightNode == null)
+ {
+ return null;
+ }
+
+ return new MemberExpression(leftNode, ".", rightNode);
+ case 'v':
+ _position += 2;
+ return ParseBinaryExpression("/");
+ case 'V':
+ _position += 2;
+ return ParseBinaryExpression("/=");
+ }
+ return null;
+ case 'e':
+ switch (Peek(1))
+ {
+ case 'o':
+ _position += 2;
+ return ParseBinaryExpression("^");
+ case 'O':
+ _position += 2;
+ return ParseBinaryExpression("^=");
+ case 'q':
+ _position += 2;
+ return ParseBinaryExpression("==");
+ }
+ return null;
+ case 'g':
+ switch (Peek(1))
+ {
+ case 'e':
+ _position += 2;
+ return ParseBinaryExpression(">=");
+ case 't':
+ _position += 2;
+ return ParseBinaryExpression(">");
+ }
+ return null;
+ case 'i':
+ switch (Peek(1))
+ {
+ case 'x':
+ _position += 2;
+ BaseNode Base = ParseExpression();
+ if (Base == null)
+ {
+ return null;
+ }
+
+ BaseNode subscript = ParseExpression();
+ if (Base == null)
+ {
+ return null;
+ }
+
+ return new ArraySubscriptingExpression(Base, subscript);
+ case 'l':
+ _position += 2;
+
+ List<BaseNode> bracedExpressions = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ expression = ParseBracedExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ bracedExpressions.Add(expression);
+ }
+ return new InitListExpression(null, bracedExpressions);
+ }
+ return null;
+ case 'l':
+ switch (Peek(1))
+ {
+ case 'e':
+ _position += 2;
+ return ParseBinaryExpression("<=");
+ case 's':
+ _position += 2;
+ return ParseBinaryExpression("<<");
+ case 'S':
+ _position += 2;
+ return ParseBinaryExpression("<<=");
+ case 't':
+ _position += 2;
+ return ParseBinaryExpression("<");
+ }
+ return null;
+ case 'm':
+ switch (Peek(1))
+ {
+ case 'i':
+ _position += 2;
+ return ParseBinaryExpression("-");
+ case 'I':
+ _position += 2;
+ return ParseBinaryExpression("-=");
+ case 'l':
+ _position += 2;
+ return ParseBinaryExpression("*");
+ case 'L':
+ _position += 2;
+ return ParseBinaryExpression("*=");
+ case 'm':
+ _position += 2;
+ if (ConsumeIf("_"))
+ {
+ return ParsePrefixExpression("--");
+ }
+
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new PostfixExpression(expression, "--");
+ }
+ return null;
+ case 'n':
+ switch (Peek(1))
+ {
+ case 'a':
+ case 'w':
+ _position += 2;
+ return ParseNewExpression();
+ case 'e':
+ _position += 2;
+ return ParseBinaryExpression("!=");
+ case 'g':
+ _position += 2;
+ return ParsePrefixExpression("-");
+ case 't':
+ _position += 2;
+ return ParsePrefixExpression("!");
+ case 'x':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("noexcept (", expression, ")");
+ }
+ return null;
+ case 'o':
+ switch (Peek(1))
+ {
+ case 'n':
+ return ParseUnresolvedName();
+ case 'o':
+ _position += 2;
+ return ParseBinaryExpression("||");
+ case 'r':
+ _position += 2;
+ return ParseBinaryExpression("|");
+ case 'R':
+ _position += 2;
+ return ParseBinaryExpression("|=");
+ }
+ return null;
+ case 'p':
+ switch (Peek(1))
+ {
+ case 'm':
+ _position += 2;
+ return ParseBinaryExpression("->*");
+ case 'l':
+ case 's':
+ _position += 2;
+ return ParseBinaryExpression("+");
+ case 'L':
+ _position += 2;
+ return ParseBinaryExpression("+=");
+ case 'p':
+ _position += 2;
+ if (ConsumeIf("_"))
+ {
+ return ParsePrefixExpression("++");
+ }
+
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new PostfixExpression(expression, "++");
+ case 't':
+ _position += 2;
+ leftNode = ParseExpression();
+ if (leftNode == null)
+ {
+ return null;
+ }
+
+ rightNode = ParseExpression();
+ if (rightNode == null)
+ {
+ return null;
+ }
+
+ return new MemberExpression(leftNode, "->", rightNode);
+ }
+ return null;
+ case 'q':
+ if (Peek(1) == 'u')
+ {
+ _position += 2;
+ BaseNode condition = ParseExpression();
+ if (condition == null)
+ {
+ return null;
+ }
+
+ leftNode = ParseExpression();
+ if (leftNode == null)
+ {
+ return null;
+ }
+
+ rightNode = ParseExpression();
+ if (rightNode == null)
+ {
+ return null;
+ }
+
+ return new ConditionalExpression(condition, leftNode, rightNode);
+ }
+ return null;
+ case 'r':
+ switch (Peek(1))
+ {
+ case 'c':
+ _position += 2;
+ BaseNode to = ParseType();
+ if (to == null)
+ {
+ return null;
+ }
+
+ BaseNode from = ParseExpression();
+ if (from == null)
+ {
+ return null;
+ }
+
+ return new CastExpression("reinterpret_cast", to, from);
+ case 'm':
+ _position += 2;
+ return ParseBinaryExpression("%");
+ case 'M':
+ _position += 2;
+ return ParseBinaryExpression("%");
+ case 's':
+ _position += 2;
+ return ParseBinaryExpression(">>");
+ case 'S':
+ _position += 2;
+ return ParseBinaryExpression(">>=");
+ }
+ return null;
+ case 's':
+ switch (Peek(1))
+ {
+ case 'c':
+ _position += 2;
+ BaseNode to = ParseType();
+ if (to == null)
+ {
+ return null;
+ }
+
+ BaseNode from = ParseExpression();
+ if (from == null)
+ {
+ return null;
+ }
+
+ return new CastExpression("static_cast", to, from);
+ case 'p':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new PackedTemplateParameterExpansion(expression);
+ case 'r':
+ return ParseUnresolvedName();
+ case 't':
+ _position += 2;
+ BaseNode enclosedType = ParseType();
+ if (enclosedType == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("sizeof (", enclosedType, ")");
+ case 'z':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("sizeof (", expression, ")");
+ case 'Z':
+ _position += 2;
+ BaseNode sizeofParamNode = null;
+ switch (Peek())
+ {
+ case 'T':
+ // FIXME: ??? Not entire sure if it's right
+ sizeofParamNode = ParseFunctionParameter();
+ if (sizeofParamNode == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("sizeof...(", new PackedTemplateParameterExpansion(sizeofParamNode), ")");
+ case 'f':
+ sizeofParamNode = ParseFunctionParameter();
+ if (sizeofParamNode == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("sizeof...(", sizeofParamNode, ")");
+ }
+ return null;
+ case 'P':
+ _position += 2;
+ List<BaseNode> arguments = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ BaseNode argument = ParseTemplateArgument();
+ if (argument == null)
+ {
+ return null;
+ }
+
+ arguments.Add(argument);
+ }
+ return new EnclosedExpression("sizeof...(", new NodeArray(arguments), ")");
+ }
+ return null;
+ case 't':
+ switch (Peek(1))
+ {
+ case 'e':
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("typeid (", expression, ")");
+ case 't':
+ BaseNode enclosedType = ParseExpression();
+ if (enclosedType == null)
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("typeid (", enclosedType, ")");
+ case 'l':
+ _position += 2;
+ BaseNode typeNode = ParseType();
+ if (typeNode == null)
+ {
+ return null;
+ }
+
+ List<BaseNode> bracedExpressions = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ expression = ParseBracedExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ bracedExpressions.Add(expression);
+ }
+ return new InitListExpression(typeNode, bracedExpressions);
+ case 'r':
+ _position += 2;
+ return new NameType("throw");
+ case 'w':
+ _position += 2;
+ expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ return new ThrowExpression(expression);
+ }
+ return null;
+ }
+
+ if (char.IsDigit(Peek()))
+ {
+ return ParseUnresolvedName();
+ }
+
+ return null;
+ }
+
+ private BaseNode ParseIntegerLiteral(string literalName)
+ {
+ string number = ParseNumber(true);
+ if (number == null || number.Length == 0 || !ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ return new IntegerLiteral(literalName, number);
+ }
+
+ // <expr-primary> ::= L <type> <value number> E # integer literal
+ // ::= L <type> <value float> E # floating literal (TODO)
+ // ::= L <string type> E # string literal
+ // ::= L <nullptr type> E # nullptr literal (i.e., "LDnE")
+ // ::= L <pointer type> 0 E # null pointer template argument
+ // ::= L <type> <real-part float> _ <imag-part float> E # complex floating point literal (C 2000)
+ // ::= L _Z <encoding> E # external name
+ private BaseNode ParseExpressionPrimary()
+ {
+ if (!ConsumeIf("L"))
+ {
+ return null;
+ }
+
+ switch (Peek())
+ {
+ case 'w':
+ _position++;
+ return ParseIntegerLiteral("wchar_t");
+ case 'b':
+ if (ConsumeIf("b0E"))
+ {
+ return new NameType("false", NodeType.BooleanExpression);
+ }
+
+ if (ConsumeIf("b1E"))
+ {
+ return new NameType("true", NodeType.BooleanExpression);
+ }
+
+ return null;
+ case 'c':
+ _position++;
+ return ParseIntegerLiteral("char");
+ case 'a':
+ _position++;
+ return ParseIntegerLiteral("signed char");
+ case 'h':
+ _position++;
+ return ParseIntegerLiteral("unsigned char");
+ case 's':
+ _position++;
+ return ParseIntegerLiteral("short");
+ case 't':
+ _position++;
+ return ParseIntegerLiteral("unsigned short");
+ case 'i':
+ _position++;
+ return ParseIntegerLiteral("");
+ case 'j':
+ _position++;
+ return ParseIntegerLiteral("u");
+ case 'l':
+ _position++;
+ return ParseIntegerLiteral("l");
+ case 'm':
+ _position++;
+ return ParseIntegerLiteral("ul");
+ case 'x':
+ _position++;
+ return ParseIntegerLiteral("ll");
+ case 'y':
+ _position++;
+ return ParseIntegerLiteral("ull");
+ case 'n':
+ _position++;
+ return ParseIntegerLiteral("__int128");
+ case 'o':
+ _position++;
+ return ParseIntegerLiteral("unsigned __int128");
+ case 'd':
+ case 'e':
+ case 'f':
+ // TODO: floating literal
+ return null;
+ case '_':
+ if (ConsumeIf("_Z"))
+ {
+ BaseNode encoding = ParseEncoding();
+ if (encoding != null && ConsumeIf("E"))
+ {
+ return encoding;
+ }
+ }
+ return null;
+ case 'T':
+ return null;
+ default:
+ BaseNode type = ParseType();
+ if (type == null)
+ {
+ return null;
+ }
+
+ string number = ParseNumber();
+ if (number == null || number.Length == 0 || !ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ return new IntegerCastExpression(type, number);
+ }
+ }
+
+ // <decltype> ::= Dt <expression> E # decltype of an id-expression or class member access (C++0x)
+ // ::= DT <expression> E # decltype of an expression (C++0x)
+ private BaseNode ParseDecltype()
+ {
+ if (!ConsumeIf("D") || (!ConsumeIf("t") && !ConsumeIf("T")))
+ {
+ return null;
+ }
+
+ BaseNode expression = ParseExpression();
+ if (expression == null)
+ {
+ return null;
+ }
+
+ if (!ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ return new EnclosedExpression("decltype(", expression, ")");
+ }
+
+ // <template-param> ::= T_ # first template parameter
+ // ::= T <parameter-2 non-negative number> _
+ // <template-template-param> ::= <template-param>
+ // ::= <substitution>
+ private BaseNode ParseTemplateParam()
+ {
+ if (!ConsumeIf("T"))
+ {
+ return null;
+ }
+
+ int index = 0;
+ if (!ConsumeIf("_"))
+ {
+ index = ParsePositiveNumber();
+ if (index < 0)
+ {
+ return null;
+ }
+
+ index++;
+ if (!ConsumeIf("_"))
+ {
+ return null;
+ }
+ }
+
+ // 5.1.8: TODO: lambda?
+ // if (IsParsingLambdaParameters)
+ // return new NameType("auto");
+
+ if (_canForwardTemplateReference)
+ {
+ ForwardTemplateReference forwardTemplateReference = new ForwardTemplateReference(index);
+ _forwardTemplateReferenceList.Add(forwardTemplateReference);
+ return forwardTemplateReference;
+ }
+ if (index >= _templateParamList.Count)
+ {
+ return null;
+ }
+
+ return _templateParamList[index];
+ }
+
+ // <template-args> ::= I <template-arg>+ E
+ private BaseNode ParseTemplateArguments(bool hasContext = false)
+ {
+ if (!ConsumeIf("I"))
+ {
+ return null;
+ }
+
+ if (hasContext)
+ {
+ _templateParamList.Clear();
+ }
+
+ List<BaseNode> args = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ if (hasContext)
+ {
+ List<BaseNode> templateParamListTemp = new List<BaseNode>(_templateParamList);
+ BaseNode templateArgument = ParseTemplateArgument();
+ _templateParamList = templateParamListTemp;
+ if (templateArgument == null)
+ {
+ return null;
+ }
+
+ args.Add(templateArgument);
+ if (templateArgument.GetType().Equals(NodeType.PackedTemplateArgument))
+ {
+ templateArgument = new PackedTemplateParameter(((NodeArray)templateArgument).Nodes);
+ }
+ _templateParamList.Add(templateArgument);
+ }
+ else
+ {
+ BaseNode templateArgument = ParseTemplateArgument();
+ if (templateArgument == null)
+ {
+ return null;
+ }
+
+ args.Add(templateArgument);
+ }
+ }
+ return new TemplateArguments(args);
+ }
+
+
+ // <template-arg> ::= <type> # type or template
+ // ::= X <expression> E # expression
+ // ::= <expr-primary> # simple expressions
+ // ::= J <template-arg>* E # argument pack
+ private BaseNode ParseTemplateArgument()
+ {
+ switch (Peek())
+ {
+ // X <expression> E
+ case 'X':
+ _position++;
+ BaseNode expression = ParseExpression();
+ if (expression == null || !ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ return expression;
+ // <expr-primary>
+ case 'L':
+ return ParseExpressionPrimary();
+ // J <template-arg>* E
+ case 'J':
+ _position++;
+ List<BaseNode> templateArguments = new List<BaseNode>();
+ while (!ConsumeIf("E"))
+ {
+ BaseNode templateArgument = ParseTemplateArgument();
+ if (templateArgument == null)
+ {
+ return null;
+ }
+
+ templateArguments.Add(templateArgument);
+ }
+ return new NodeArray(templateArguments, NodeType.PackedTemplateArgument);
+ // <type>
+ default:
+ return ParseType();
+ }
+ }
+
+ class NameParserContext
+ {
+ public CvType Cv;
+ public SimpleReferenceType Ref;
+ public bool FinishWithTemplateArguments;
+ public bool CtorDtorConversion;
+ }
+
+
+ // <unresolved-type> ::= <template-param> [ <template-args> ] # T:: or T<X,Y>::
+ // ::= <decltype> # decltype(p)::
+ // ::= <substitution>
+ private BaseNode ParseUnresolvedType()
+ {
+ if (Peek() == 'T')
+ {
+ BaseNode templateParam = ParseTemplateParam();
+ if (templateParam == null)
+ {
+ return null;
+ }
+
+ _substitutionList.Add(templateParam);
+ return templateParam;
+ }
+ else if (Peek() == 'D')
+ {
+ BaseNode declType = ParseDecltype();
+ if (declType == null)
+ {
+ return null;
+ }
+
+ _substitutionList.Add(declType);
+ return declType;
+ }
+ return ParseSubstitution();
+ }
+
+ // <simple-id> ::= <source-name> [ <template-args> ]
+ private BaseNode ParseSimpleId()
+ {
+ BaseNode sourceName = ParseSourceName();
+ if (sourceName == null)
+ {
+ return null;
+ }
+
+ if (Peek() == 'I')
+ {
+ BaseNode templateArguments = ParseTemplateArguments();
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ return new NameTypeWithTemplateArguments(sourceName, templateArguments);
+ }
+ return sourceName;
+ }
+
+ // <destructor-name> ::= <unresolved-type> # e.g., ~T or ~decltype(f())
+ // ::= <simple-id> # e.g., ~A<2*N>
+ private BaseNode ParseDestructorName()
+ {
+ BaseNode node;
+ if (char.IsDigit(Peek()))
+ {
+ node = ParseSimpleId();
+ }
+ else
+ {
+ node = ParseUnresolvedType();
+ }
+ if (node == null)
+ {
+ return null;
+ }
+
+ return new DtorName(node);
+ }
+
+ // <base-unresolved-name> ::= <simple-id> # unresolved name
+ // extension ::= <operator-name> # unresolved operator-function-id
+ // extension ::= <operator-name> <template-args> # unresolved operator template-id
+ // ::= on <operator-name> # unresolved operator-function-id
+ // ::= on <operator-name> <template-args> # unresolved operator template-id
+ // ::= dn <destructor-name> # destructor or pseudo-destructor;
+ // # e.g. ~X or ~X<N-1>
+ private BaseNode ParseBaseUnresolvedName()
+ {
+ if (char.IsDigit(Peek()))
+ {
+ return ParseSimpleId();
+ }
+ else if (ConsumeIf("dn"))
+ {
+ return ParseDestructorName();
+ }
+
+ ConsumeIf("on");
+ BaseNode operatorName = ParseOperatorName(null);
+ if (operatorName == null)
+ {
+ return null;
+ }
+
+ if (Peek() == 'I')
+ {
+ BaseNode templateArguments = ParseTemplateArguments();
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ return new NameTypeWithTemplateArguments(operatorName, templateArguments);
+ }
+ return operatorName;
+ }
+
+ // <unresolved-name> ::= [gs] <base-unresolved-name> # x or (with "gs") ::x
+ // ::= sr <unresolved-type> <base-unresolved-name> # T::x / decltype(p)::x
+ // ::= srN <unresolved-type> <unresolved-qualifier-level>+ E <base-unresolved-name>
+ // # T::N::x /decltype(p)::N::x
+ // ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name>
+ // # A::x, N::y, A<T>::z; "gs" means leading "::"
+ private BaseNode ParseUnresolvedName(NameParserContext context = null)
+ {
+ BaseNode result = null;
+ if (ConsumeIf("srN"))
+ {
+ result = ParseUnresolvedType();
+ if (result == null)
+ {
+ return null;
+ }
+
+ if (Peek() == 'I')
+ {
+ BaseNode templateArguments = ParseTemplateArguments();
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ result = new NameTypeWithTemplateArguments(result, templateArguments);
+ if (result == null)
+ {
+ return null;
+ }
+ }
+
+ while (!ConsumeIf("E"))
+ {
+ BaseNode simpleId = ParseSimpleId();
+ if (simpleId == null)
+ {
+ return null;
+ }
+
+ result = new QualifiedName(result, simpleId);
+ if (result == null)
+ {
+ return null;
+ }
+ }
+
+ BaseNode baseName = ParseBaseUnresolvedName();
+ if (baseName == null)
+ {
+ return null;
+ }
+
+ return new QualifiedName(result, baseName);
+ }
+
+ bool isGlobal = ConsumeIf("gs");
+
+ // ::= [gs] <base-unresolved-name> # x or (with "gs") ::x
+ if (!ConsumeIf("sr"))
+ {
+ result = ParseBaseUnresolvedName();
+ if (result == null)
+ {
+ return null;
+ }
+
+ if (isGlobal)
+ {
+ result = new GlobalQualifiedName(result);
+ }
+
+ return result;
+ }
+
+ // ::= [gs] sr <unresolved-qualifier-level>+ E <base-unresolved-name>
+ if (char.IsDigit(Peek()))
+ {
+ do
+ {
+ BaseNode qualifier = ParseSimpleId();
+ if (qualifier == null)
+ {
+ return null;
+ }
+
+ if (result != null)
+ {
+ result = new QualifiedName(result, qualifier);
+ }
+ else if (isGlobal)
+ {
+ result = new GlobalQualifiedName(qualifier);
+ }
+ else
+ {
+ result = qualifier;
+ }
+
+ if (result == null)
+ {
+ return null;
+ }
+ } while (!ConsumeIf("E"));
+ }
+ // ::= sr <unresolved-type> [template-args] <base-unresolved-name> # T::x / decltype(p)::x
+ else
+ {
+ result = ParseUnresolvedType();
+ if (result == null)
+ {
+ return null;
+ }
+
+ if (Peek() == 'I')
+ {
+ BaseNode templateArguments = ParseTemplateArguments();
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ result = new NameTypeWithTemplateArguments(result, templateArguments);
+ if (result == null)
+ {
+ return null;
+ }
+ }
+ }
+
+ if (result == null)
+ {
+ return null;
+ }
+
+ BaseNode baseUnresolvedName = ParseBaseUnresolvedName();
+ if (baseUnresolvedName == null)
+ {
+ return null;
+ }
+
+ return new QualifiedName(result, baseUnresolvedName);
+ }
+
+ // <unscoped-name> ::= <unqualified-name>
+ // ::= St <unqualified-name> # ::std::
+ private BaseNode ParseUnscopedName(NameParserContext context)
+ {
+ if (ConsumeIf("St"))
+ {
+ BaseNode unresolvedName = ParseUnresolvedName(context);
+ if (unresolvedName == null)
+ {
+ return null;
+ }
+
+ return new StdQualifiedName(unresolvedName);
+ }
+ return ParseUnresolvedName(context);
+ }
+
+ // <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix (TODO)> <unqualified-name> E
+ // ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix (TODO)> <template-args (TODO)> E
+ private BaseNode ParseNestedName(NameParserContext context)
+ {
+ // Impossible in theory
+ if (Consume() != 'N')
+ {
+ return null;
+ }
+
+ BaseNode result = null;
+ CvType cv = new CvType(ParseCvQualifiers(), null);
+ if (context != null)
+ {
+ context.Cv = cv;
+ }
+
+ SimpleReferenceType Ref = ParseRefQualifiers();
+ if (context != null)
+ {
+ context.Ref = Ref;
+ }
+
+ if (ConsumeIf("St"))
+ {
+ result = new NameType("std");
+ }
+
+ while (!ConsumeIf("E"))
+ {
+ // <data-member-prefix> end
+ if (ConsumeIf("M"))
+ {
+ if (result == null)
+ {
+ return null;
+ }
+
+ continue;
+ }
+ char c = Peek();
+
+ // TODO: template args
+ if (c == 'T')
+ {
+ BaseNode templateParam = ParseTemplateParam();
+ if (templateParam == null)
+ {
+ return null;
+ }
+
+ result = CreateNameNode(result, templateParam, context);
+ _substitutionList.Add(result);
+ continue;
+ }
+
+ // <template-prefix> <template-args>
+ if (c == 'I')
+ {
+ BaseNode templateArgument = ParseTemplateArguments(context != null);
+ if (templateArgument == null || result == null)
+ {
+ return null;
+ }
+
+ result = new NameTypeWithTemplateArguments(result, templateArgument);
+ if (context != null)
+ {
+ context.FinishWithTemplateArguments = true;
+ }
+
+ _substitutionList.Add(result);
+ continue;
+ }
+
+ // <decltype>
+ if (c == 'D' && (Peek(1) == 't' || Peek(1) == 'T'))
+ {
+ BaseNode decltype = ParseDecltype();
+ if (decltype == null)
+ {
+ return null;
+ }
+
+ result = CreateNameNode(result, decltype, context);
+ _substitutionList.Add(result);
+ continue;
+ }
+
+ // <substitution>
+ if (c == 'S' && Peek(1) != 't')
+ {
+ BaseNode substitution = ParseSubstitution();
+ if (substitution == null)
+ {
+ return null;
+ }
+
+ result = CreateNameNode(result, substitution, context);
+ if (result != substitution)
+ {
+ _substitutionList.Add(substitution);
+ }
+
+ continue;
+ }
+
+ // <ctor-dtor-name> of ParseUnqualifiedName
+ if (c == 'C' || (c == 'D' && Peek(1) != 'C'))
+ {
+ // We cannot have nothing before this
+ if (result == null)
+ {
+ return null;
+ }
+
+ BaseNode ctOrDtorName = ParseCtorDtorName(context, result);
+
+ if (ctOrDtorName == null)
+ {
+ return null;
+ }
+
+ result = CreateNameNode(result, ctOrDtorName, context);
+
+ // TODO: ABI Tags (before)
+ if (result == null)
+ {
+ return null;
+ }
+
+ _substitutionList.Add(result);
+ continue;
+ }
+
+ BaseNode unqualifiedName = ParseUnqualifiedName(context);
+ if (unqualifiedName == null)
+ {
+ return null;
+ }
+ result = CreateNameNode(result, unqualifiedName, context);
+
+ _substitutionList.Add(result);
+ }
+ if (result == null || _substitutionList.Count == 0)
+ {
+ return null;
+ }
+
+ _substitutionList.RemoveAt(_substitutionList.Count - 1);
+ return result;
+ }
+
+ // <discriminator> ::= _ <non-negative number> # when number < 10
+ // ::= __ <non-negative number> _ # when number >= 10
+ private void ParseDiscriminator()
+ {
+ if (Count() == 0)
+ {
+ return;
+ }
+ // We ignore the discriminator, we don't need it.
+ if (ConsumeIf("_"))
+ {
+ ConsumeIf("_");
+ while (char.IsDigit(Peek()) && Count() != 0)
+ {
+ Consume();
+ }
+ ConsumeIf("_");
+ }
+ }
+
+ // <local-name> ::= Z <function encoding> E <entity name> [<discriminator>]
+ // ::= Z <function encoding> E s [<discriminator>]
+ // ::= Z <function encoding> Ed [ <parameter number> ] _ <entity name>
+ private BaseNode ParseLocalName(NameParserContext context)
+ {
+ if (!ConsumeIf("Z"))
+ {
+ return null;
+ }
+
+ BaseNode encoding = ParseEncoding();
+ if (encoding == null || !ConsumeIf("E"))
+ {
+ return null;
+ }
+
+ BaseNode entityName;
+ if (ConsumeIf("s"))
+ {
+ ParseDiscriminator();
+ return new LocalName(encoding, new NameType("string literal"));
+ }
+ else if (ConsumeIf("d"))
+ {
+ ParseNumber(true);
+ if (!ConsumeIf("_"))
+ {
+ return null;
+ }
+
+ entityName = ParseName(context);
+ if (entityName == null)
+ {
+ return null;
+ }
+
+ return new LocalName(encoding, entityName);
+ }
+
+ entityName = ParseName(context);
+ if (entityName == null)
+ {
+ return null;
+ }
+
+ ParseDiscriminator();
+ return new LocalName(encoding, entityName);
+ }
+
+ // <name> ::= <nested-name>
+ // ::= <unscoped-name>
+ // ::= <unscoped-template-name> <template-args>
+ // ::= <local-name> # See Scope Encoding below (TODO)
+ private BaseNode ParseName(NameParserContext context = null)
+ {
+ ConsumeIf("L");
+
+ if (Peek() == 'N')
+ {
+ return ParseNestedName(context);
+ }
+
+ if (Peek() == 'Z')
+ {
+ return ParseLocalName(context);
+ }
+
+ if (Peek() == 'S' && Peek(1) != 't')
+ {
+ BaseNode substitution = ParseSubstitution();
+ if (substitution == null)
+ {
+ return null;
+ }
+
+ if (Peek() != 'I')
+ {
+ return null;
+ }
+
+ BaseNode templateArguments = ParseTemplateArguments(context != null);
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ if (context != null)
+ {
+ context.FinishWithTemplateArguments = true;
+ }
+
+ return new NameTypeWithTemplateArguments(substitution, templateArguments);
+ }
+
+ BaseNode result = ParseUnscopedName(context);
+ if (result == null)
+ {
+ return null;
+ }
+
+ if (Peek() == 'I')
+ {
+ _substitutionList.Add(result);
+ BaseNode templateArguments = ParseTemplateArguments(context != null);
+ if (templateArguments == null)
+ {
+ return null;
+ }
+
+ if (context != null)
+ {
+ context.FinishWithTemplateArguments = true;
+ }
+
+ return new NameTypeWithTemplateArguments(result, templateArguments);
+ }
+
+ return result;
+ }
+
+ private bool IsEncodingEnd()
+ {
+ char c = Peek();
+ return Count() == 0 || c == 'E' || c == '.' || c == '_';
+ }
+
+ // <encoding> ::= <function name> <bare-function-type>
+ // ::= <data name>
+ // ::= <special-name>
+ private BaseNode ParseEncoding()
+ {
+ NameParserContext context = new NameParserContext();
+ if (Peek() == 'T' || (Peek() == 'G' && Peek(1) == 'V'))
+ {
+ return ParseSpecialName(context);
+ }
+
+ BaseNode name = ParseName(context);
+ if (name == null)
+ {
+ return null;
+ }
+
+ // TODO: compute template refs here
+
+ if (IsEncodingEnd())
+ {
+ return name;
+ }
+
+ // TODO: Ua9enable_ifI
+
+ BaseNode returnType = null;
+ if (!context.CtorDtorConversion && context.FinishWithTemplateArguments)
+ {
+ returnType = ParseType();
+ if (returnType == null)
+ {
+ return null;
+ }
+ }
+
+ if (ConsumeIf("v"))
+ {
+ return new EncodedFunction(name, null, context.Cv, context.Ref, null, returnType);
+ }
+
+ List<BaseNode> Params = new List<BaseNode>();
+
+ // backup because that can be destroyed by parseType
+ CvType cv = context.Cv;
+ SimpleReferenceType Ref = context.Ref;
+
+ while (!IsEncodingEnd())
+ {
+ BaseNode param = ParseType();
+ if (param == null)
+ {
+ return null;
+ }
+
+ Params.Add(param);
+ }
+
+ return new EncodedFunction(name, new NodeArray(Params), cv, Ref, null, returnType);
+ }
+
+ // <mangled-name> ::= _Z <encoding>
+ // ::= <type>
+ private BaseNode Parse()
+ {
+ if (ConsumeIf("_Z"))
+ {
+ BaseNode encoding = ParseEncoding();
+ if (encoding != null && Count() == 0)
+ {
+ return encoding;
+ }
+ return null;
+ }
+ else
+ {
+ BaseNode type = ParseType();
+ if (type != null && Count() == 0)
+ {
+ return type;
+ }
+ return null;
+ }
+ }
+
+ public static string Parse(string originalMangled)
+ {
+ Demangler instance = new Demangler(originalMangled);
+ BaseNode resNode = instance.Parse();
+
+ if (resNode != null)
+ {
+ StringWriter writer = new StringWriter();
+ resNode.Print(writer);
+ return writer.ToString();
+ }
+
+ return originalMangled;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs b/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
new file mode 100644
index 00000000..59bc881f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS
+{
+ class HomebrewRomFsStream : Stream
+ {
+ private Stream _baseStream;
+ private long _positionOffset;
+
+ public HomebrewRomFsStream(Stream baseStream, long positionOffset)
+ {
+ _baseStream = baseStream;
+ _positionOffset = positionOffset;
+
+ _baseStream.Position = _positionOffset;
+ }
+
+ public override bool CanRead => _baseStream.CanRead;
+
+ public override bool CanSeek => _baseStream.CanSeek;
+
+ public override bool CanWrite => false;
+
+ public override long Length => _baseStream.Length - _positionOffset;
+
+ public override long Position
+ {
+ get
+ {
+ return _baseStream.Position - _positionOffset;
+ }
+ set
+ {
+ _baseStream.Position = value + _positionOffset;
+ }
+ }
+
+ public override void Flush()
+ {
+ _baseStream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return _baseStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
+ {
+ offset += _positionOffset;
+ }
+
+ return _baseStream.Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _baseStream.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
new file mode 100644
index 00000000..1639532e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -0,0 +1,556 @@
+using LibHac.Common;
+using LibHac.Common.Keys;
+using LibHac.Fs;
+using LibHac.Fs.Shim;
+using LibHac.FsSystem;
+using LibHac.Tools.FsSystem;
+using Ryujinx.Audio;
+using Ryujinx.Audio.Input;
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Output;
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
+using Ryujinx.HLE.HOS.Services.Apm;
+using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
+using Ryujinx.HLE.HOS.Services.Caps;
+using Ryujinx.HLE.HOS.Services.Mii;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+using Ryujinx.HLE.HOS.Services.Nv;
+using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
+using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
+using Ryujinx.HLE.HOS.Services.Sdb.Pl;
+using Ryujinx.HLE.HOS.Services.Settings;
+using Ryujinx.HLE.HOS.Services.Sm;
+using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Processes;
+using Ryujinx.Horizon;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using TimeSpanType = Ryujinx.HLE.HOS.Services.Time.Clock.TimeSpanType;
+
+namespace Ryujinx.HLE.HOS
+{
+ using TimeServiceManager = Services.Time.TimeManager;
+
+ public class Horizon : IDisposable
+ {
+ internal const int HidSize = 0x40000;
+ internal const int FontSize = 0x1100000;
+ internal const int IirsSize = 0x8000;
+ internal const int TimeSize = 0x1000;
+ internal const int AppletCaptureBufferSize = 0x384000;
+
+ internal KernelContext KernelContext { get; }
+
+ internal Switch Device { get; private set; }
+
+ internal ITickSource TickSource { get; }
+
+ internal SurfaceFlinger SurfaceFlinger { get; private set; }
+ internal AudioManager AudioManager { get; private set; }
+ internal AudioOutputManager AudioOutputManager { get; private set; }
+ internal AudioInputManager AudioInputManager { get; private set; }
+ internal AudioRendererManager AudioRendererManager { get; private set; }
+ internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; }
+
+ public SystemStateMgr State { get; private set; }
+
+ internal PerformanceState PerformanceState { get; private set; }
+
+ internal AppletStateMgr AppletState { get; private set; }
+
+ internal List<NfpDevice> NfpDevices { get; private set; }
+
+ internal SmRegistry SmRegistry { get; private set; }
+
+ internal ServerBase SmServer { get; private set; }
+ internal ServerBase BsdServer { get; private set; }
+ internal ServerBase AudRenServer { get; private set; }
+ internal ServerBase AudOutServer { get; private set; }
+ internal ServerBase FsServer { get; private set; }
+ internal ServerBase HidServer { get; private set; }
+ internal ServerBase NvDrvServer { get; private set; }
+ internal ServerBase TimeServer { get; private set; }
+ internal ServerBase ViServer { get; private set; }
+ internal ServerBase ViServerM { get; private set; }
+ internal ServerBase ViServerS { get; private set; }
+
+ internal KSharedMemory HidSharedMem { get; private set; }
+ internal KSharedMemory FontSharedMem { get; private set; }
+ internal KSharedMemory IirsSharedMem { get; private set; }
+
+ internal KTransferMemory AppletCaptureBufferTransfer { get; private set; }
+
+ internal SharedFontManager SharedFontManager { get; private set; }
+ internal AccountManager AccountManager { get; private set; }
+ internal ContentManager ContentManager { get; private set; }
+ internal CaptureManager CaptureManager { get; private set; }
+
+ internal KEvent VsyncEvent { get; private set; }
+
+ internal KEvent DisplayResolutionChangeEvent { get; private set; }
+
+ public KeySet KeySet => Device.FileSystem.KeySet;
+
+ private bool _isDisposed;
+
+ public bool EnablePtc { get; set; }
+
+ public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
+
+ public int GlobalAccessLogMode { get; set; }
+
+ internal SharedMemoryStorage HidStorage { get; private set; }
+
+ internal NvHostSyncpt HostSyncpoint { get; private set; }
+
+ internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
+
+ internal ServiceTable ServiceTable { get; private set; }
+
+ public bool IsPaused { get; private set; }
+
+ public Horizon(Switch device)
+ {
+ TickSource = new TickSource(KernelConstants.CounterFrequency);
+
+ KernelContext = new KernelContext(
+ TickSource,
+ device,
+ device.Memory,
+ device.Configuration.MemoryConfiguration.ToKernelMemorySize(),
+ device.Configuration.MemoryConfiguration.ToKernelMemoryArrange());
+
+ Device = device;
+
+ State = new SystemStateMgr();
+
+ PerformanceState = new PerformanceState();
+
+ NfpDevices = new List<NfpDevice>();
+
+ // Note: This is not really correct, but with HLE of services, the only memory
+ // region used that is used is Application, so we can use the other ones for anything.
+ KMemoryRegionManager region = KernelContext.MemoryManager.MemoryRegions[(int)MemoryRegion.NvServices];
+
+ ulong hidPa = region.Address;
+ ulong fontPa = region.Address + HidSize;
+ ulong iirsPa = region.Address + HidSize + FontSize;
+ ulong timePa = region.Address + HidSize + FontSize + IirsSize;
+ ulong appletCaptureBufferPa = region.Address + HidSize + FontSize + IirsSize + TimeSize;
+
+ KPageList hidPageList = new KPageList();
+ KPageList fontPageList = new KPageList();
+ KPageList iirsPageList = new KPageList();
+ KPageList timePageList = new KPageList();
+ KPageList appletCaptureBufferPageList = new KPageList();
+
+ hidPageList.AddRange(hidPa, HidSize / KPageTableBase.PageSize);
+ fontPageList.AddRange(fontPa, FontSize / KPageTableBase.PageSize);
+ iirsPageList.AddRange(iirsPa, IirsSize / KPageTableBase.PageSize);
+ timePageList.AddRange(timePa, TimeSize / KPageTableBase.PageSize);
+ appletCaptureBufferPageList.AddRange(appletCaptureBufferPa, AppletCaptureBufferSize / KPageTableBase.PageSize);
+
+ var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList);
+ var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList);
+ var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList);
+ var timeStorage = new SharedMemoryStorage(KernelContext, timePageList);
+ var appletCaptureBufferStorage = new SharedMemoryStorage(KernelContext, appletCaptureBufferPageList);
+
+ HidStorage = hidStorage;
+
+ HidSharedMem = new KSharedMemory(KernelContext, hidStorage, 0, 0, KMemoryPermission.Read);
+ FontSharedMem = new KSharedMemory(KernelContext, fontStorage, 0, 0, KMemoryPermission.Read);
+ IirsSharedMem = new KSharedMemory(KernelContext, iirsStorage, 0, 0, KMemoryPermission.Read);
+
+ KSharedMemory timeSharedMemory = new KSharedMemory(KernelContext, timeStorage, 0, 0, KMemoryPermission.Read);
+
+ TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timeStorage, TimeSize);
+
+ AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage);
+
+ AppletState = new AppletStateMgr(this);
+
+ AppletState.SetFocus(true);
+
+ VsyncEvent = new KEvent(KernelContext);
+
+ DisplayResolutionChangeEvent = new KEvent(KernelContext);
+
+ SharedFontManager = new SharedFontManager(device, fontStorage);
+ AccountManager = device.Configuration.AccountManager;
+ ContentManager = device.Configuration.ContentManager;
+ CaptureManager = new CaptureManager(device);
+
+ LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
+
+ // TODO: use set:sys (and get external clock source id from settings)
+ // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
+ UInt128 clockSourceId = UInt128Utils.CreateRandom();
+ IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+ // We assume the rtc is system time.
+ TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue);
+
+ // Configure and setup internal offset
+ TimeSpanType internalOffset = TimeSpanType.FromSeconds(device.Configuration.SystemTimeOffset);
+
+ TimeSpanType systemTimeOffset = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds);
+
+ if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime())
+ {
+ internalOffset = internalOffset.AddSeconds(3600L);
+ }
+ else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime())
+ {
+ internalOffset = internalOffset.AddSeconds(-3600L);
+ }
+
+ internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
+
+ // First init the standard steady clock
+ TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
+ TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
+
+ if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
+ {
+ TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
+
+ // The network system clock needs a valid system clock, as such we setup this system clock using the local system clock.
+ TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
+ TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy);
+ }
+
+ TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, false, SteadyClockTimePoint.GetRandom());
+
+ // FIXME: TimeZone should be init here but it's actually done in ContentManager
+
+ TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
+
+ DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient);
+
+ HostSyncpoint = new NvHostSyncpt(device);
+
+ SurfaceFlinger = new SurfaceFlinger(device);
+
+ InitializeAudioRenderer(TickSource);
+ InitializeServices();
+ }
+
+ private void InitializeAudioRenderer(ITickSource tickSource)
+ {
+ AudioManager = new AudioManager();
+ AudioOutputManager = new AudioOutputManager();
+ AudioInputManager = new AudioInputManager();
+ AudioRendererManager = new AudioRendererManager(tickSource);
+ AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
+ AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
+
+ IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
+
+ for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
+ {
+ KEvent registerBufferEvent = new KEvent(KernelContext);
+
+ audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
+ }
+
+ AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
+ AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
+
+ IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
+
+ for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
+ {
+ KEvent registerBufferEvent = new KEvent(KernelContext);
+
+ audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
+ }
+
+ AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents);
+
+ IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
+
+ for (int i = 0; i < systemEvents.Length; i++)
+ {
+ KEvent systemEvent = new KEvent(KernelContext);
+
+ systemEvents[i] = new AudioKernelEvent(systemEvent);
+ }
+
+ AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
+
+ AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver);
+
+ AudioManager.Start();
+ }
+
+ private void InitializeServices()
+ {
+ SmRegistry = new SmRegistry();
+ SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
+
+ // Wait until SM server thread is done with initialization,
+ // only then doing connections to SM is safe.
+ SmServer.InitDone.WaitOne();
+
+ BsdServer = new ServerBase(KernelContext, "BsdServer");
+ AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
+ AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
+ FsServer = new ServerBase(KernelContext, "FsServer");
+ HidServer = new ServerBase(KernelContext, "HidServer");
+ NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
+ TimeServer = new ServerBase(KernelContext, "TimeServer");
+ ViServer = new ServerBase(KernelContext, "ViServerU");
+ ViServerM = new ServerBase(KernelContext, "ViServerM");
+ ViServerS = new ServerBase(KernelContext, "ViServerS");
+
+ StartNewServices();
+ }
+
+ private void StartNewServices()
+ {
+ ServiceTable = new ServiceTable();
+ var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices));
+
+ foreach (var service in services)
+ {
+ const ProcessCreationFlags flags =
+ ProcessCreationFlags.EnableAslr |
+ ProcessCreationFlags.AddressSpace64Bit |
+ ProcessCreationFlags.Is64Bit |
+ ProcessCreationFlags.PoolPartitionSystem;
+
+ ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
+
+ uint[] defaultCapabilities = new uint[]
+ {
+ 0x030363F7,
+ 0x1FFFFFCF,
+ 0x207FFFEF,
+ 0x47E0060F,
+ 0x0048BFFF,
+ 0x01007FFF
+ };
+
+ // TODO:
+ // - Pass enough information (capabilities, process creation info, etc) on ServiceEntry for proper initialization.
+ // - Have the ThreadStart function take the syscall, address space and thread context parameters instead of passing them here.
+ KernelStatic.StartInitialProcess(KernelContext, creationInfo, defaultCapabilities, 44, () =>
+ {
+ service.Start(KernelContext.Syscall, KernelStatic.GetCurrentProcess().CpuMemory, KernelStatic.GetCurrentThread().ThreadContext);
+ });
+ }
+ }
+
+ public bool LoadKip(string kipPath)
+ {
+ using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
+
+ return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
+ }
+
+ public void ChangeDockedModeState(bool newState)
+ {
+ if (newState != State.DockedMode)
+ {
+ State.DockedMode = newState;
+ PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
+
+ AppletState.Messages.Enqueue(AppletMessage.OperationModeChanged);
+ AppletState.Messages.Enqueue(AppletMessage.PerformanceModeChanged);
+ AppletState.MessageEvent.ReadableEvent.Signal();
+
+ SignalDisplayResolutionChange();
+
+ Device.Configuration.RefreshInputConfig?.Invoke();
+ }
+ }
+
+ public void SetVolume(float volume)
+ {
+ AudioOutputManager.SetVolume(volume);
+ AudioRendererManager.SetVolume(volume);
+ }
+
+ public float GetVolume()
+ {
+ return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume();
+ }
+
+ public void ReturnFocus()
+ {
+ AppletState.SetFocus(true);
+ }
+
+ public void SimulateWakeUpMessage()
+ {
+ AppletState.Messages.Enqueue(AppletMessage.Resume);
+ AppletState.MessageEvent.ReadableEvent.Signal();
+ }
+
+ public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid)
+ {
+ if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag)
+ {
+ NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound;
+ NfpDevices[nfpDeviceId].AmiiboId = amiiboId;
+ NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid;
+ }
+ }
+
+ public bool SearchingForAmiibo(out int nfpDeviceId)
+ {
+ nfpDeviceId = default;
+
+ for (int i = 0; i < NfpDevices.Count; i++)
+ {
+ if (NfpDevices[i].State == NfpDeviceState.SearchingForTag)
+ {
+ nfpDeviceId = i;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void SignalDisplayResolutionChange()
+ {
+ DisplayResolutionChangeEvent.ReadableEvent.Signal();
+ }
+
+ public void SignalVsync()
+ {
+ VsyncEvent.ReadableEvent.Signal();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_isDisposed && disposing)
+ {
+ _isDisposed = true;
+
+ // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
+ if (IsPaused)
+ {
+ AudioManager.StopUpdates();
+
+ TogglePauseEmulation(false);
+
+ AudioRendererManager.StopSendingCommands();
+ }
+
+ KProcess terminationProcess = new KProcess(KernelContext);
+ KThread terminationThread = new KThread(KernelContext);
+
+ terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () =>
+ {
+ // Force all threads to exit.
+ lock (KernelContext.Processes)
+ {
+ // Terminate application.
+ foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.IsApplication))
+ {
+ process.Terminate();
+ process.DecrementReferenceCount();
+ }
+
+ // The application existed, now surface flinger can exit too.
+ SurfaceFlinger.Dispose();
+
+ // Terminate HLE services (must be done after the application is already terminated,
+ // otherwise the application will receive errors due to service termination).
+ foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.IsApplication))
+ {
+ process.Terminate();
+ process.DecrementReferenceCount();
+ }
+
+ KernelContext.Processes.Clear();
+ }
+
+ // Exit ourself now!
+ KernelStatic.GetCurrentThread().Exit();
+ });
+
+ terminationThread.Start();
+
+ // Wait until the thread is actually started.
+ while (terminationThread.HostThread.ThreadState == ThreadState.Unstarted)
+ {
+ Thread.Sleep(10);
+ }
+
+ // Wait until the termination thread is done terminating all the other threads.
+ terminationThread.HostThread.Join();
+
+ // Destroy nvservices channels as KThread could be waiting on some user events.
+ // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade.
+ INvDrvServices.Destroy();
+
+ AudioManager.Dispose();
+ AudioOutputManager.Dispose();
+ AudioInputManager.Dispose();
+
+ AudioRendererManager.Dispose();
+
+ if (LibHacHorizonManager.ApplicationClient != null)
+ {
+ LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
+ }
+
+ KernelContext.Dispose();
+ }
+ }
+
+ public void TogglePauseEmulation(bool pause)
+ {
+ lock (KernelContext.Processes)
+ {
+ foreach (KProcess process in KernelContext.Processes.Values)
+ {
+ if (process.IsApplication)
+ {
+ // Only game process should be paused.
+ process.SetActivity(pause);
+ }
+ }
+
+ if (pause && !IsPaused)
+ {
+ Device.AudioDeviceDriver.GetPauseEvent().Reset();
+ TickSource.Suspend();
+ }
+ else if (!pause && IsPaused)
+ {
+ Device.AudioDeviceDriver.GetPauseEvent().Set();
+ TickSource.Resume();
+ }
+ }
+ IsPaused = pause;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/IdDictionary.cs b/src/Ryujinx.HLE/HOS/IdDictionary.cs
new file mode 100644
index 00000000..5ae720ea
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/IdDictionary.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS
+{
+ class IdDictionary
+ {
+ private ConcurrentDictionary<int, object> _objs;
+
+ public ICollection<object> Values => _objs.Values;
+
+ public IdDictionary()
+ {
+ _objs = new ConcurrentDictionary<int, object>();
+ }
+
+ public bool Add(int id, object data)
+ {
+ return _objs.TryAdd(id, data);
+ }
+
+ public int Add(object data)
+ {
+ for (int id = 1; id < int.MaxValue; id++)
+ {
+ if (_objs.TryAdd(id, data))
+ {
+ return id;
+ }
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ public object GetData(int id)
+ {
+ if (_objs.TryGetValue(id, out object data))
+ {
+ return data;
+ }
+
+ return null;
+ }
+
+ public T GetData<T>(int id)
+ {
+ if (_objs.TryGetValue(id, out object data) && data is T)
+ {
+ return (T)data;
+ }
+
+ return default(T);
+ }
+
+ public object Delete(int id)
+ {
+ if (_objs.TryRemove(id, out object obj))
+ {
+ return obj;
+ }
+
+ return null;
+ }
+
+ public ICollection<object> Clear()
+ {
+ ICollection<object> values = _objs.Values;
+
+ _objs.Clear();
+
+ return values;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
new file mode 100644
index 00000000..b61d5697
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcBuffDesc
+ {
+ public ulong Position { get; private set; }
+ public ulong Size { get; private set; }
+ public byte Flags { get; private set; }
+
+ public IpcBuffDesc(BinaryReader reader)
+ {
+ ulong word0 = reader.ReadUInt32();
+ ulong word1 = reader.ReadUInt32();
+ ulong word2 = reader.ReadUInt32();
+
+ Position = word1;
+ Position |= (word2 << 4) & 0x0f00000000;
+ Position |= (word2 << 34) & 0x7000000000;
+
+ Size = word0;
+ Size |= (word2 << 8) & 0xf00000000;
+
+ Flags = (byte)(word2 & 3);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
new file mode 100644
index 00000000..c7ef7e9c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
@@ -0,0 +1,93 @@
+using Microsoft.IO;
+using Ryujinx.Common;
+using Ryujinx.Common.Memory;
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ class IpcHandleDesc
+ {
+ public bool HasPId { get; private set; }
+
+ public ulong PId { get; private set; }
+
+ public int[] ToCopy { get; private set; }
+ public int[] ToMove { get; private set; }
+
+ public IpcHandleDesc(BinaryReader reader)
+ {
+ int word = reader.ReadInt32();
+
+ HasPId = (word & 1) != 0;
+
+ PId = HasPId ? reader.ReadUInt64() : 0;
+
+ int toCopySize = (word >> 1) & 0xf;
+ int[] toCopy = toCopySize == 0 ? Array.Empty<int>() : new int[toCopySize];
+
+ for (int index = 0; index < toCopy.Length; index++)
+ {
+ toCopy[index] = reader.ReadInt32();
+ }
+
+ ToCopy = toCopy;
+
+ int toMoveSize = (word >> 5) & 0xf;
+ int[] toMove = toMoveSize == 0 ? Array.Empty<int>() : new int[toMoveSize];
+
+ for (int index = 0; index < toMove.Length; index++)
+ {
+ toMove[index] = reader.ReadInt32();
+ }
+
+ ToMove = toMove;
+ }
+
+ public IpcHandleDesc(int[] copy, int[] move)
+ {
+ ToCopy = copy ?? throw new ArgumentNullException(nameof(copy));
+ ToMove = move ?? throw new ArgumentNullException(nameof(move));
+ }
+
+ public IpcHandleDesc(int[] copy, int[] move, ulong pId) : this(copy, move)
+ {
+ PId = pId;
+
+ HasPId = true;
+ }
+
+ public static IpcHandleDesc MakeCopy(params int[] handles)
+ {
+ return new IpcHandleDesc(handles, Array.Empty<int>());
+ }
+
+ public static IpcHandleDesc MakeMove(params int[] handles)
+ {
+ return new IpcHandleDesc(Array.Empty<int>(), handles);
+ }
+
+ public RecyclableMemoryStream GetStream()
+ {
+ RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
+
+ int word = HasPId ? 1 : 0;
+
+ word |= (ToCopy.Length & 0xf) << 1;
+ word |= (ToMove.Length & 0xf) << 5;
+
+ ms.Write(word);
+
+ if (HasPId)
+ {
+ ms.Write(PId);
+ }
+
+ ms.Write(ToCopy);
+ ms.Write(ToMove);
+
+ ms.Position = 0;
+ return ms;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs
new file mode 100644
index 00000000..72770b90
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ abstract class IpcMagic
+ {
+ public const long Sfci = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
+ public const long Sfco = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
new file mode 100644
index 00000000..21630c42
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
@@ -0,0 +1,283 @@
+using Microsoft.IO;
+using Ryujinx.Common;
+using Ryujinx.Common.Memory;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ class IpcMessage
+ {
+ public IpcMessageType Type { get; set; }
+
+ public IpcHandleDesc HandleDesc { get; set; }
+
+ public List<IpcPtrBuffDesc> PtrBuff { get; private set; }
+ public List<IpcBuffDesc> SendBuff { get; private set; }
+ public List<IpcBuffDesc> ReceiveBuff { get; private set; }
+ public List<IpcBuffDesc> ExchangeBuff { get; private set; }
+ public List<IpcRecvListBuffDesc> RecvListBuff { get; private set; }
+
+ public List<int> ObjectIds { get; private set; }
+
+ public byte[] RawData { get; set; }
+
+ public IpcMessage()
+ {
+ PtrBuff = new List<IpcPtrBuffDesc>(0);
+ SendBuff = new List<IpcBuffDesc>(0);
+ ReceiveBuff = new List<IpcBuffDesc>(0);
+ ExchangeBuff = new List<IpcBuffDesc>(0);
+ RecvListBuff = new List<IpcRecvListBuffDesc>(0);
+
+ ObjectIds = new List<int>(0);
+ }
+
+ public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr)
+ {
+ using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data))
+ {
+ BinaryReader reader = new BinaryReader(ms);
+
+ int word0 = reader.ReadInt32();
+ int word1 = reader.ReadInt32();
+
+ Type = (IpcMessageType)(word0 & 0xffff);
+
+ int ptrBuffCount = (word0 >> 16) & 0xf;
+ int sendBuffCount = (word0 >> 20) & 0xf;
+ int recvBuffCount = (word0 >> 24) & 0xf;
+ int xchgBuffCount = (word0 >> 28) & 0xf;
+
+ int rawDataSize = (word1 >> 0) & 0x3ff;
+ int recvListFlags = (word1 >> 10) & 0xf;
+ bool hndDescEnable = ((word1 >> 31) & 0x1) != 0;
+
+ if (hndDescEnable)
+ {
+ HandleDesc = new IpcHandleDesc(reader);
+ }
+
+ PtrBuff = new List<IpcPtrBuffDesc>(ptrBuffCount);
+
+ for (int index = 0; index < ptrBuffCount; index++)
+ {
+ PtrBuff.Add(new IpcPtrBuffDesc(reader));
+ }
+
+ static List<IpcBuffDesc> ReadBuff(BinaryReader reader, int count)
+ {
+ List<IpcBuffDesc> buff = new List<IpcBuffDesc>(count);
+
+ for (int index = 0; index < count; index++)
+ {
+ buff.Add(new IpcBuffDesc(reader));
+ }
+
+ return buff;
+ }
+
+ SendBuff = ReadBuff(reader, sendBuffCount);
+ ReceiveBuff = ReadBuff(reader, recvBuffCount);
+ ExchangeBuff = ReadBuff(reader, xchgBuffCount);
+
+ rawDataSize *= 4;
+
+ long recvListPos = reader.BaseStream.Position + rawDataSize;
+
+ // Only CMIF has the padding requirements.
+ if (Type < IpcMessageType.TipcCloseSession)
+ {
+ long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr);
+
+ if (rawDataSize != 0)
+ {
+ rawDataSize -= (int)pad0;
+ }
+
+ reader.BaseStream.Seek(pad0, SeekOrigin.Current);
+ }
+
+ int recvListCount = recvListFlags - 2;
+
+ if (recvListCount == 0)
+ {
+ recvListCount = 1;
+ }
+ else if (recvListCount < 0)
+ {
+ recvListCount = 0;
+ }
+
+ RawData = reader.ReadBytes(rawDataSize);
+
+ reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin);
+
+ RecvListBuff = new List<IpcRecvListBuffDesc>(recvListCount);
+
+ for (int index = 0; index < recvListCount; index++)
+ {
+ RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
+ }
+
+ ObjectIds = new List<int>(0);
+ }
+ }
+
+ public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
+ {
+ RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
+
+ int word0;
+ int word1;
+
+ word0 = (int)Type;
+ word0 |= (PtrBuff.Count & 0xf) << 16;
+ word0 |= (SendBuff.Count & 0xf) << 20;
+ word0 |= (ReceiveBuff.Count & 0xf) << 24;
+ word0 |= (ExchangeBuff.Count & 0xf) << 28;
+
+ using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
+
+ int dataLength = RawData?.Length ?? 0;
+
+ dataLength = (dataLength + 3) & ~3;
+
+ int rawLength = dataLength;
+
+ int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8);
+
+ // Apparently, padding after Raw Data is 16 bytes, however when there is
+ // padding before Raw Data too, we need to subtract the size of this padding.
+ // This is the weirdest padding I've seen so far...
+ int pad1 = 0x10 - pad0;
+
+ dataLength = (dataLength + pad0 + pad1) / 4;
+
+ word1 = (dataLength & 0x3ff) | (2 << 10);
+
+ if (HandleDesc != null)
+ {
+ word1 |= 1 << 31;
+ }
+
+ ms.Write(word0);
+ ms.Write(word1);
+
+ if (handleDataStream != null)
+ {
+ ms.Write(handleDataStream);
+ }
+
+ foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff)
+ {
+ ms.Write(ptrBuffDesc.GetWord0());
+ ms.Write(ptrBuffDesc.GetWord1());
+ }
+
+ ms.WriteByte(0, pad0);
+
+ if (RawData != null)
+ {
+ ms.Write(RawData);
+ ms.WriteByte(0, rawLength - RawData.Length);
+ }
+
+ ms.WriteByte(0, pad1);
+
+ ms.Write(recvListAddr);
+
+ ms.Position = 0;
+
+ return ms;
+ }
+
+ public RecyclableMemoryStream GetStreamTipc()
+ {
+ Debug.Assert(PtrBuff.Count == 0);
+
+ RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
+
+ int word0;
+ int word1;
+
+ word0 = (int)Type;
+ word0 |= (SendBuff.Count & 0xf) << 20;
+ word0 |= (ReceiveBuff.Count & 0xf) << 24;
+ word0 |= (ExchangeBuff.Count & 0xf) << 28;
+
+ using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
+
+ int dataLength = RawData?.Length ?? 0;
+
+ dataLength = ((dataLength + 3) & ~3) / 4;
+
+ word1 = (dataLength & 0x3ff);
+
+ if (HandleDesc != null)
+ {
+ word1 |= 1 << 31;
+ }
+
+ ms.Write(word0);
+ ms.Write(word1);
+
+ if (handleDataStream != null)
+ {
+ ms.Write(handleDataStream);
+ }
+
+ if (RawData != null)
+ {
+ ms.Write(RawData);
+ }
+
+ return ms;
+ }
+
+ private long GetPadSize16(long position)
+ {
+ if ((position & 0xf) != 0)
+ {
+ return 0x10 - (position & 0xf);
+ }
+
+ return 0;
+ }
+
+ // ReSharper disable once InconsistentNaming
+ public (ulong Position, ulong Size) GetBufferType0x21(int index = 0)
+ {
+ if (PtrBuff.Count > index && PtrBuff[index].Position != 0)
+ {
+ return (PtrBuff[index].Position, PtrBuff[index].Size);
+ }
+
+ if (SendBuff.Count > index)
+ {
+ return (SendBuff[index].Position, SendBuff[index].Size);
+ }
+
+ return (0, 0);
+ }
+
+ // ReSharper disable once InconsistentNaming
+ public (ulong Position, ulong Size) GetBufferType0x22(int index = 0)
+ {
+ if (RecvListBuff.Count > index && RecvListBuff[index].Position != 0)
+ {
+ return (RecvListBuff[index].Position, RecvListBuff[index].Size);
+ }
+
+ if (ReceiveBuff.Count > index)
+ {
+ return (ReceiveBuff[index].Position, ReceiveBuff[index].Size);
+ }
+
+ return (0, 0);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
new file mode 100644
index 00000000..1c862248
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ enum IpcMessageType
+ {
+ CmifResponse = 0,
+ CmifCloseSession = 2,
+ CmifRequest = 4,
+ CmifControl = 5,
+ CmifRequestWithContext = 6,
+ CmifControlWithContext = 7,
+ TipcCloseSession = 0xF
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
new file mode 100644
index 00000000..05798fe1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
@@ -0,0 +1,58 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcPtrBuffDesc
+ {
+ public ulong Position { get; private set; }
+ public uint Index { get; private set; }
+ public ulong Size { get; private set; }
+
+ public IpcPtrBuffDesc(ulong position, uint index, ulong size)
+ {
+ Position = position;
+ Index = index;
+ Size = size;
+ }
+
+ public IpcPtrBuffDesc(BinaryReader reader)
+ {
+ ulong word0 = reader.ReadUInt32();
+ ulong word1 = reader.ReadUInt32();
+
+ Position = word1;
+ Position |= (word0 << 20) & 0x0f00000000;
+ Position |= (word0 << 30) & 0x7000000000;
+
+ Index = ((uint)word0 >> 0) & 0x03f;
+ Index |= ((uint)word0 >> 3) & 0x1c0;
+
+ Size = (ushort)(word0 >> 16);
+ }
+
+ public IpcPtrBuffDesc WithSize(ulong size)
+ {
+ return new IpcPtrBuffDesc(Position, Index, size);
+ }
+
+ public uint GetWord0()
+ {
+ uint word0;
+
+ word0 = (uint)((Position & 0x0f00000000) >> 20);
+ word0 |= (uint)((Position & 0x7000000000) >> 30);
+
+ word0 |= (Index & 0x03f) << 0;
+ word0 |= (Index & 0x1c0) << 3;
+
+ word0 |= (uint)Size << 16;
+
+ return word0;
+ }
+
+ public uint GetWord1()
+ {
+ return (uint)Position;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
new file mode 100644
index 00000000..bcc9d8f8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
@@ -0,0 +1,23 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcRecvListBuffDesc
+ {
+ public ulong Position { get; private set; }
+ public ulong Size { get; private set; }
+
+ public IpcRecvListBuffDesc(ulong position, ulong size)
+ {
+ Position = position;
+ Size = size;
+ }
+
+ public IpcRecvListBuffDesc(ulong packedValue)
+ {
+ Position = packedValue & 0xffffffffffff;
+
+ Size = (ushort)(packedValue >> 48);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs b/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs
new file mode 100644
index 00000000..b3aaa219
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ delegate long ServiceProcessRequest(ServiceCtx context);
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs
new file mode 100644
index 00000000..473683ff
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ interface IKFutureSchedulerObject
+ {
+ void TimeUp();
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
new file mode 100644
index 00000000..424bf788
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
@@ -0,0 +1,73 @@
+using Ryujinx.Horizon.Common;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KAutoObject
+ {
+ protected KernelContext KernelContext;
+
+ private int _referenceCount;
+
+ public KAutoObject(KernelContext context)
+ {
+ KernelContext = context;
+
+ _referenceCount = 1;
+ }
+
+ public virtual Result SetName(string name)
+ {
+ if (!KernelContext.AutoObjectNames.TryAdd(name, this))
+ {
+ return KernelResult.InvalidState;
+ }
+
+ return Result.Success;
+ }
+
+ public static Result RemoveName(KernelContext context, string name)
+ {
+ if (!context.AutoObjectNames.TryRemove(name, out _))
+ {
+ return KernelResult.NotFound;
+ }
+
+ return Result.Success;
+ }
+
+ public static KAutoObject FindNamedObject(KernelContext context, string name)
+ {
+ if (context.AutoObjectNames.TryGetValue(name, out KAutoObject obj))
+ {
+ return obj;
+ }
+
+ return null;
+ }
+
+ public void IncrementReferenceCount()
+ {
+ int newRefCount = Interlocked.Increment(ref _referenceCount);
+
+ Debug.Assert(newRefCount >= 2);
+ }
+
+ public void DecrementReferenceCount()
+ {
+ int newRefCount = Interlocked.Decrement(ref _referenceCount);
+
+ Debug.Assert(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ Destroy();
+ }
+ }
+
+ protected virtual void Destroy()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs
new file mode 100644
index 00000000..b1a602f1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs
@@ -0,0 +1,188 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KResourceLimit : KAutoObject
+ {
+ private const int DefaultTimeoutMs = 10000; // 10s
+
+ private readonly long[] _current;
+ private readonly long[] _limit;
+ private readonly long[] _current2;
+ private readonly long[] _peak;
+
+ private readonly object _lock;
+
+ private readonly LinkedList<KThread> _waitingThreads;
+
+ private int _waitingThreadsCount;
+
+ public KResourceLimit(KernelContext context) : base(context)
+ {
+ _current = new long[(int)LimitableResource.Count];
+ _limit = new long[(int)LimitableResource.Count];
+ _current2 = new long[(int)LimitableResource.Count];
+ _peak = new long[(int)LimitableResource.Count];
+
+ _lock = new object();
+
+ _waitingThreads = new LinkedList<KThread>();
+ }
+
+ public bool Reserve(LimitableResource resource, ulong amount)
+ {
+ return Reserve(resource, (long)amount);
+ }
+
+ public bool Reserve(LimitableResource resource, long amount)
+ {
+ return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(DefaultTimeoutMs));
+ }
+
+ public bool Reserve(LimitableResource resource, long amount, long timeout)
+ {
+ long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout);
+
+ endTimePoint += PerformanceCounter.ElapsedMilliseconds;
+
+ bool success = false;
+
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ if (_current2[index] >= _limit[index])
+ {
+ return false;
+ }
+
+ long newCurrent = _current[index] + amount;
+
+ while (newCurrent > _limit[index] && _current2[index] + amount <= _limit[index])
+ {
+ _waitingThreadsCount++;
+
+ KConditionVariable.Wait(KernelContext, _waitingThreads, _lock, timeout);
+
+ _waitingThreadsCount--;
+
+ newCurrent = _current[index] + amount;
+
+ if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint)
+ {
+ break;
+ }
+ }
+
+ if (newCurrent <= _limit[index])
+ {
+ _current[index] = newCurrent;
+ _current2[index] += amount;
+
+ if (_current[index] > _peak[index])
+ {
+ _peak[index] = _current[index];
+ }
+
+ success = true;
+ }
+ }
+
+ return success;
+ }
+
+ public void Release(LimitableResource resource, ulong amount)
+ {
+ Release(resource, (long)amount);
+ }
+
+ public void Release(LimitableResource resource, long amount)
+ {
+ Release(resource, amount, amount);
+ }
+
+ public void Release(LimitableResource resource, long amount, long amount2)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ _current[index] -= amount;
+ _current2[index] -= amount2;
+
+ if (_waitingThreadsCount > 0)
+ {
+ KConditionVariable.NotifyAll(KernelContext, _waitingThreads);
+ }
+ }
+ }
+
+ public long GetRemainingValue(LimitableResource resource)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ return _limit[index] - _current[index];
+ }
+ }
+
+ public long GetCurrentValue(LimitableResource resource)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ return _current[index];
+ }
+ }
+
+ public long GetLimitValue(LimitableResource resource)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ return _limit[index];
+ }
+ }
+
+ public long GetPeakValue(LimitableResource resource)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ return _peak[index];
+ }
+ }
+
+ public Result SetLimitValue(LimitableResource resource, long limit)
+ {
+ int index = GetIndex(resource);
+
+ lock (_lock)
+ {
+ if (_current[index] <= limit)
+ {
+ _limit[index] = limit;
+ _peak[index] = _current[index];
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidState;
+ }
+ }
+ }
+
+ private static int GetIndex(LimitableResource resource)
+ {
+ return (int)resource;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
new file mode 100644
index 00000000..ddc0069d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KSynchronizationObject : KAutoObject
+ {
+ public LinkedList<KThread> WaitingThreads { get; }
+
+ public KSynchronizationObject(KernelContext context) : base(context)
+ {
+ WaitingThreads = new LinkedList<KThread>();
+ }
+
+ public LinkedListNode<KThread> AddWaitingThread(KThread thread)
+ {
+ return WaitingThreads.AddLast(thread);
+ }
+
+ public void RemoveWaitingThread(LinkedListNode<KThread> node)
+ {
+ WaitingThreads.Remove(node);
+ }
+
+ public virtual void Signal()
+ {
+ KernelContext.Synchronization.SignalObject(this);
+ }
+
+ public virtual bool IsSignaled()
+ {
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs
new file mode 100644
index 00000000..8a727c30
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs
@@ -0,0 +1,78 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ static class KSystemControl
+ {
+ private const ulong KiB = 1024;
+ private const ulong MiB = 1024 * KiB;
+ private const ulong GiB = 1024 * MiB;
+
+ private const ulong PageSize = 4 * KiB;
+
+ private const ulong RequiredNonSecureSystemPoolSizeVi = 0x2238 * PageSize;
+ private const ulong RequiredNonSecureSystemPoolSizeNvservices = 0x710 * PageSize;
+ private const ulong RequiredNonSecureSystemPoolSizeOther = 0x80 * PageSize;
+
+ private const ulong RequiredNonSecureSystemPoolSize =
+ RequiredNonSecureSystemPoolSizeVi +
+ RequiredNonSecureSystemPoolSizeNvservices +
+ RequiredNonSecureSystemPoolSizeOther;
+
+ public static ulong GetApplicationPoolSize(MemoryArrange arrange)
+ {
+ return arrange switch
+ {
+ MemoryArrange.MemoryArrange4GiB or
+ MemoryArrange.MemoryArrange4GiBSystemDev or
+ MemoryArrange.MemoryArrange6GiBAppletDev => 3285 * MiB,
+ MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
+ MemoryArrange.MemoryArrange6GiB or
+ MemoryArrange.MemoryArrange8GiB => 4916 * MiB,
+ _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\".")
+ };
+ }
+
+ public static ulong GetAppletPoolSize(MemoryArrange arrange)
+ {
+ return arrange switch
+ {
+ MemoryArrange.MemoryArrange4GiB => 507 * MiB,
+ MemoryArrange.MemoryArrange4GiBAppletDev => 1554 * MiB,
+ MemoryArrange.MemoryArrange4GiBSystemDev => 448 * MiB,
+ MemoryArrange.MemoryArrange6GiB => 562 * MiB,
+ MemoryArrange.MemoryArrange6GiBAppletDev or
+ MemoryArrange.MemoryArrange8GiB => 2193 * MiB,
+ _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\".")
+ };
+ }
+
+ public static ulong GetMinimumNonSecureSystemPoolSize()
+ {
+ return RequiredNonSecureSystemPoolSize;
+ }
+
+ public static ulong GetDramEndAddress(MemorySize size)
+ {
+ return DramMemoryMap.DramBase + GetDramSize(size);
+ }
+
+ public static ulong GenerateRandom()
+ {
+ // TODO
+ return 0;
+ }
+
+ public static ulong GetDramSize(MemorySize size)
+ {
+ return size switch
+ {
+ MemorySize.MemorySize4GiB => 4 * GiB,
+ MemorySize.MemorySize6GiB => 6 * GiB,
+ MemorySize.MemorySize8GiB => 8 * GiB,
+ _ => throw new ArgumentException($"Invalid memory size \"{size}\".")
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
new file mode 100644
index 00000000..c0cd9ce9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
@@ -0,0 +1,218 @@
+using Ryujinx.Common;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class KTimeManager : IDisposable
+ {
+ public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
+
+ private class WaitingObject
+ {
+ public IKFutureSchedulerObject Object { get; }
+ public long TimePoint { get; }
+
+ public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
+ {
+ Object = schedulerObj;
+ TimePoint = timePoint;
+ }
+ }
+
+ private readonly KernelContext _context;
+ private readonly List<WaitingObject> _waitingObjects;
+ private AutoResetEvent _waitEvent;
+ private bool _keepRunning;
+ private long _enforceWakeupFromSpinWait;
+
+ private const long NanosecondsPerSecond = 1000000000L;
+ private const long NanosecondsPerMillisecond = 1000000L;
+
+ public KTimeManager(KernelContext context)
+ {
+ _context = context;
+ _waitingObjects = new List<WaitingObject>();
+ _keepRunning = true;
+
+ Thread work = new Thread(WaitAndCheckScheduledObjects)
+ {
+ Name = "HLE.TimeManager"
+ };
+
+ work.Start();
+ }
+
+ public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
+ {
+ long startTime = PerformanceCounter.ElapsedTicks;
+ long timePoint = startTime + ConvertNanosecondsToHostTicks(timeout);
+
+ if (timePoint < startTime)
+ {
+ timePoint = long.MaxValue;
+ }
+
+ lock (_context.CriticalSection.Lock)
+ {
+ _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
+
+ if (timeout < NanosecondsPerMillisecond)
+ {
+ Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
+ }
+ }
+
+ _waitEvent.Set();
+ }
+
+ public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj)
+ {
+ lock (_context.CriticalSection.Lock)
+ {
+ for (int index = _waitingObjects.Count - 1; index >= 0; index--)
+ {
+ if (_waitingObjects[index].Object == schedulerObj)
+ {
+ _waitingObjects.RemoveAt(index);
+ }
+ }
+ }
+ }
+
+ private void WaitAndCheckScheduledObjects()
+ {
+ SpinWait spinWait = new SpinWait();
+ WaitingObject next;
+
+ using (_waitEvent = new AutoResetEvent(false))
+ {
+ while (_keepRunning)
+ {
+ lock (_context.CriticalSection.Lock)
+ {
+ Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
+
+ next = GetNextWaitingObject();
+ }
+
+ if (next != null)
+ {
+ long timePoint = PerformanceCounter.ElapsedTicks;
+
+ if (next.TimePoint > timePoint)
+ {
+ long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue);
+
+ if (ms > 0)
+ {
+ _waitEvent.WaitOne((int)ms);
+ }
+ else
+ {
+ while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks < next.TimePoint)
+ {
+ // Our time is close - don't let SpinWait go off and potentially Thread.Sleep().
+ if (spinWait.NextSpinWillYield)
+ {
+ Thread.Yield();
+
+ spinWait.Reset();
+ }
+ else
+ {
+ spinWait.SpinOnce();
+ }
+ }
+
+ spinWait.Reset();
+ }
+ }
+
+ bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint;
+
+ if (timeUp)
+ {
+ lock (_context.CriticalSection.Lock)
+ {
+ if (_waitingObjects.Remove(next))
+ {
+ next.Object.TimeUp();
+ }
+ }
+ }
+ }
+ else
+ {
+ _waitEvent.WaitOne();
+ }
+ }
+ }
+ }
+
+ private WaitingObject GetNextWaitingObject()
+ {
+ WaitingObject selected = null;
+
+ long lowestTimePoint = long.MaxValue;
+
+ for (int index = _waitingObjects.Count - 1; index >= 0; index--)
+ {
+ WaitingObject current = _waitingObjects[index];
+
+ if (current.TimePoint <= lowestTimePoint)
+ {
+ selected = current;
+ lowestTimePoint = current.TimePoint;
+ }
+ }
+
+ return selected;
+ }
+
+ public static long ConvertNanosecondsToMilliseconds(long time)
+ {
+ time /= NanosecondsPerMillisecond;
+
+ if ((ulong)time > int.MaxValue)
+ {
+ return int.MaxValue;
+ }
+
+ return time;
+ }
+
+ public static long ConvertMillisecondsToNanoseconds(long time)
+ {
+ return time * NanosecondsPerMillisecond;
+ }
+
+ public static long ConvertNanosecondsToHostTicks(long ns)
+ {
+ long nsDiv = ns / NanosecondsPerSecond;
+ long nsMod = ns % NanosecondsPerSecond;
+ long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond;
+ long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond;
+
+ long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond;
+ return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
+ }
+
+ public static long ConvertGuestTicksToNanoseconds(long ticks)
+ {
+ return (long)Math.Ceiling(ticks * (1000000000.0 / 19200000.0));
+ }
+
+ public static long ConvertHostTicksToTicks(long time)
+ {
+ return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);
+ }
+
+ public void Dispose()
+ {
+ _keepRunning = false;
+ _waitEvent?.Set();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs
new file mode 100644
index 00000000..efa2a480
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs
@@ -0,0 +1,89 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ static class KernelInit
+ {
+ private readonly struct MemoryRegion
+ {
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public ulong EndAddress => Address + Size;
+
+ public MemoryRegion(ulong address, ulong size)
+ {
+ Address = address;
+ Size = size;
+ }
+ }
+
+ public static void InitializeResourceLimit(KResourceLimit resourceLimit, MemorySize size)
+ {
+ void EnsureSuccess(Result result)
+ {
+ if (result != Result.Success)
+ {
+ throw new InvalidOperationException($"Unexpected result \"{result}\".");
+ }
+ }
+
+ ulong ramSize = KSystemControl.GetDramSize(size);
+
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, (long)ramSize));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200));
+ EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900));
+
+ if (!resourceLimit.Reserve(LimitableResource.Memory, 0) ||
+ !resourceLimit.Reserve(LimitableResource.Memory, 0x60000))
+ {
+ throw new InvalidOperationException("Unexpected failure reserving memory on resource limit.");
+ }
+ }
+
+ public static KMemoryRegionManager[] GetMemoryRegions(MemorySize size, MemoryArrange arrange)
+ {
+ ulong poolEnd = KSystemControl.GetDramEndAddress(size);
+ ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange);
+ ulong appletPoolSize = KSystemControl.GetAppletPoolSize(arrange);
+
+ MemoryRegion servicePool;
+ MemoryRegion nvServicesPool;
+ MemoryRegion appletPool;
+ MemoryRegion applicationPool;
+
+ ulong nvServicesPoolSize = KSystemControl.GetMinimumNonSecureSystemPoolSize();
+
+ applicationPool = new MemoryRegion(poolEnd - applicationPoolSize, applicationPoolSize);
+
+ ulong nvServicesPoolEnd = applicationPool.Address - appletPoolSize;
+
+ nvServicesPool = new MemoryRegion(nvServicesPoolEnd - nvServicesPoolSize, nvServicesPoolSize);
+ appletPool = new MemoryRegion(nvServicesPoolEnd, appletPoolSize);
+
+ // Note: There is an extra region used by the kernel, however
+ // since we are doing HLE we are not going to use that memory, so give all
+ // the remaining memory space to services.
+ ulong servicePoolSize = nvServicesPool.Address - DramMemoryMap.SlabHeapEnd;
+
+ servicePool = new MemoryRegion(DramMemoryMap.SlabHeapEnd, servicePoolSize);
+
+ return new KMemoryRegionManager[]
+ {
+ GetMemoryRegion(applicationPool),
+ GetMemoryRegion(appletPool),
+ GetMemoryRegion(servicePool),
+ GetMemoryRegion(nvServicesPool)
+ };
+ }
+
+ private static KMemoryRegionManager GetMemoryRegion(MemoryRegion region)
+ {
+ return new KMemoryRegionManager(region.Address, region.Size, region.EndAddress);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
new file mode 100644
index 00000000..cbc276c5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
@@ -0,0 +1,73 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ static class KernelTransfer
+ {
+ public static bool UserToKernel<T>(out T value, ulong address) where T : unmanaged
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)Unsafe.SizeOf<T>()))
+ {
+ value = currentProcess.CpuMemory.Read<T>(address);
+
+ return true;
+ }
+
+ value = default;
+
+ return false;
+ }
+
+ public static bool UserToKernelArray<T>(ulong address, Span<T> values) where T : unmanaged
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Span<byte> data = MemoryMarshal.Cast<T, byte>(values);
+
+ if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)data.Length))
+ {
+ currentProcess.CpuMemory.Read(address, data);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static bool UserToKernelString(out string value, ulong address, uint size)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsRangeMapped(address, size))
+ {
+ value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, address, size);
+
+ return true;
+ }
+
+ value = null;
+
+ return false;
+ }
+
+ public static bool KernelToUser<T>(ulong address, T value) where T: unmanaged
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)Unsafe.SizeOf<T>()))
+ {
+ currentProcess.CpuMemory.Write(address, value);
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs
new file mode 100644
index 00000000..2e6a3e45
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ enum LimitableResource : byte
+ {
+ Memory = 0,
+ Thread = 1,
+ Event = 2,
+ TransferMemory = 3,
+ Session = 4,
+
+ Count = 5
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs
new file mode 100644
index 00000000..d2bcfd62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ enum MemoryArrange : byte
+ {
+ MemoryArrange4GiB,
+ MemoryArrange4GiBAppletDev,
+ MemoryArrange4GiBSystemDev,
+ MemoryArrange6GiB,
+ MemoryArrange6GiBAppletDev,
+ MemoryArrange8GiB
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs
new file mode 100644
index 00000000..159385b6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ enum MemorySize : byte
+ {
+ MemorySize4GiB = 0,
+ MemorySize6GiB = 1,
+ MemorySize8GiB = 2
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs
new file mode 100644
index 00000000..4c99f425
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs
@@ -0,0 +1,128 @@
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Common
+{
+ class MersenneTwister
+ {
+ private int _index;
+ private uint[] _mt;
+
+ public MersenneTwister(uint seed)
+ {
+ _mt = new uint[624];
+
+ _mt[0] = seed;
+
+ for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++)
+ {
+ uint prev = _mt[mtIdx - 1];
+
+ _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx);
+ }
+
+ _index = _mt.Length;
+ }
+
+ public long GenRandomNumber(long min, long max)
+ {
+ long range = max - min;
+
+ if (min == max)
+ {
+ return min;
+ }
+
+ if (range == -1)
+ {
+ // Increment would cause a overflow, special case.
+ return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu);
+ }
+
+ range++;
+
+ // This is log2(Range) plus one.
+ int nextRangeLog2 = 64 - BitOperations.LeadingZeroCount((ulong)range);
+
+ // If Range is already power of 2, subtract one to use log2(Range) directly.
+ int rangeLog2 = nextRangeLog2 - (BitOperations.IsPow2(range) ? 1 : 0);
+
+ int parts = rangeLog2 > 32 ? 2 : 1;
+ int bitsPerPart = rangeLog2 / parts;
+
+ int fullParts = parts - (rangeLog2 - parts * bitsPerPart);
+
+ uint mask = 0xffffffffu >> (32 - bitsPerPart);
+ uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart);
+
+ long randomNumber;
+
+ do
+ {
+ randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1);
+ }
+ while ((ulong)randomNumber >= (ulong)range);
+
+ return min + randomNumber;
+ }
+
+ private long GenRandomNumber(
+ int parts,
+ int fullParts,
+ int bitsPerPart,
+ uint mask,
+ uint maskPlus1)
+ {
+ long randomNumber = 0;
+
+ int part = 0;
+
+ for (; part < fullParts; part++)
+ {
+ randomNumber <<= bitsPerPart;
+ randomNumber |= GenRandomNumber() & mask;
+ }
+
+ for (; part < parts; part++)
+ {
+ randomNumber <<= bitsPerPart + 1;
+ randomNumber |= GenRandomNumber() & maskPlus1;
+ }
+
+ return randomNumber;
+ }
+
+ private uint GenRandomNumber()
+ {
+ if (_index >= _mt.Length)
+ {
+ Twist();
+ }
+
+ uint value = _mt[_index++];
+
+ value ^= value >> 11;
+ value ^= (value << 7) & 0x9d2c5680;
+ value ^= (value << 15) & 0xefc60000;
+ value ^= value >> 18;
+
+ return value;
+ }
+
+ private void Twist()
+ {
+ for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++)
+ {
+ uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff);
+
+ _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1);
+
+ if ((value & 1) != 0)
+ {
+ _mt[mtIdx] ^= 0x9908b0df;
+ }
+ }
+
+ _index = 0;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs
new file mode 100644
index 00000000..4827384e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ enum ChannelState
+ {
+ NotInitialized,
+ Open,
+ ClientDisconnected,
+ ServerDisconnected
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs
new file mode 100644
index 00000000..e28244d4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KBufferDescriptor
+ {
+ public ulong ClientAddress { get; }
+ public ulong ServerAddress { get; }
+ public ulong Size { get; }
+ public MemoryState State { get; }
+
+ public KBufferDescriptor(ulong src, ulong dst, ulong size, MemoryState state)
+ {
+ ClientAddress = src;
+ ServerAddress = dst;
+ Size = size;
+ State = state;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
new file mode 100644
index 00000000..593d2c9d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
@@ -0,0 +1,217 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.Horizon.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KBufferDescriptorTable
+ {
+ private const int MaxInternalBuffersCount = 8;
+
+ private List<KBufferDescriptor> _sendBufferDescriptors;
+ private List<KBufferDescriptor> _receiveBufferDescriptors;
+ private List<KBufferDescriptor> _exchangeBufferDescriptors;
+
+ public KBufferDescriptorTable()
+ {
+ _sendBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+ _receiveBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+ _exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+ }
+
+ public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+ {
+ return Add(_sendBufferDescriptors, src, dst, size, state);
+ }
+
+ public Result AddReceiveBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+ {
+ return Add(_receiveBufferDescriptors, src, dst, size, state);
+ }
+
+ public Result AddExchangeBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+ {
+ return Add(_exchangeBufferDescriptors, src, dst, size, state);
+ }
+
+ private Result Add(List<KBufferDescriptor> list, ulong src, ulong dst, ulong size, MemoryState state)
+ {
+ if (list.Count < MaxInternalBuffersCount)
+ {
+ list.Add(new KBufferDescriptor(src, dst, size, state));
+
+ return Result.Success;
+ }
+
+ return KernelResult.OutOfMemory;
+ }
+
+ public Result CopyBuffersToClient(KPageTableBase memoryManager)
+ {
+ Result result = CopyToClient(memoryManager, _receiveBufferDescriptors);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return CopyToClient(memoryManager, _exchangeBufferDescriptors);
+ }
+
+ private Result CopyToClient(KPageTableBase memoryManager, List<KBufferDescriptor> list)
+ {
+ foreach (KBufferDescriptor desc in list)
+ {
+ MemoryState stateMask;
+
+ switch (desc.State)
+ {
+ case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break;
+ case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break;
+ case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break;
+
+ default: return KernelResult.InvalidCombination;
+ }
+
+ MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached;
+
+ if (desc.State == MemoryState.IpcBuffer0)
+ {
+ attributeMask |= MemoryAttribute.DeviceMapped;
+ }
+
+ ulong clientAddrTruncated = BitUtils.AlignDown<ulong>(desc.ClientAddress, KPageTableBase.PageSize);
+ ulong clientAddrRounded = BitUtils.AlignUp<ulong>(desc.ClientAddress, KPageTableBase.PageSize);
+
+ // Check if address is not aligned, in this case we need to perform 2 copies.
+ if (clientAddrTruncated != clientAddrRounded)
+ {
+ ulong copySize = clientAddrRounded - desc.ClientAddress;
+
+ if (copySize > desc.Size)
+ {
+ copySize = desc.Size;
+ }
+
+ Result result = memoryManager.CopyDataFromCurrentProcess(
+ desc.ClientAddress,
+ copySize,
+ stateMask,
+ stateMask,
+ KMemoryPermission.ReadAndWrite,
+ attributeMask,
+ MemoryAttribute.None,
+ desc.ServerAddress);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+
+ ulong clientEndAddr = desc.ClientAddress + desc.Size;
+ ulong serverEndAddr = desc.ServerAddress + desc.Size;
+
+ ulong clientEndAddrTruncated = BitUtils.AlignDown<ulong>(clientEndAddr, (ulong)KPageTableBase.PageSize);
+ ulong clientEndAddrRounded = BitUtils.AlignUp<ulong>(clientEndAddr, KPageTableBase.PageSize);
+ ulong serverEndAddrTruncated = BitUtils.AlignDown<ulong>(serverEndAddr, (ulong)KPageTableBase.PageSize);
+
+ if (clientEndAddrTruncated < clientEndAddrRounded &&
+ (clientAddrTruncated == clientAddrRounded || clientAddrTruncated < clientEndAddrTruncated))
+ {
+ Result result = memoryManager.CopyDataFromCurrentProcess(
+ clientEndAddrTruncated,
+ clientEndAddr - clientEndAddrTruncated,
+ stateMask,
+ stateMask,
+ KMemoryPermission.ReadAndWrite,
+ attributeMask,
+ MemoryAttribute.None,
+ serverEndAddrTruncated);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result UnmapServerBuffers(KPageTableBase memoryManager)
+ {
+ Result result = UnmapServer(memoryManager, _sendBufferDescriptors);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = UnmapServer(memoryManager, _receiveBufferDescriptors);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return UnmapServer(memoryManager, _exchangeBufferDescriptors);
+ }
+
+ private Result UnmapServer(KPageTableBase memoryManager, List<KBufferDescriptor> list)
+ {
+ foreach (KBufferDescriptor descriptor in list)
+ {
+ Result result = memoryManager.UnmapNoAttributeIfStateEquals(
+ descriptor.ServerAddress,
+ descriptor.Size,
+ descriptor.State);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result RestoreClientBuffers(KPageTableBase memoryManager)
+ {
+ Result result = RestoreClient(memoryManager, _sendBufferDescriptors);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = RestoreClient(memoryManager, _receiveBufferDescriptors);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return RestoreClient(memoryManager, _exchangeBufferDescriptors);
+ }
+
+ private Result RestoreClient(KPageTableBase memoryManager, List<KBufferDescriptor> list)
+ {
+ foreach (KBufferDescriptor descriptor in list)
+ {
+ Result result = memoryManager.UnmapIpcRestorePermission(
+ descriptor.ClientAddress,
+ descriptor.Size,
+ descriptor.State);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+
+ return Result.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs
new file mode 100644
index 00000000..eb7c5a41
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs
@@ -0,0 +1,144 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KClientPort : KSynchronizationObject
+ {
+ private int _sessionsCount;
+ private readonly int _maxSessions;
+
+ private readonly KPort _parent;
+
+ public bool IsLight => _parent.IsLight;
+
+ public KClientPort(KernelContext context, KPort parent, int maxSessions) : base(context)
+ {
+ _maxSessions = maxSessions;
+ _parent = parent;
+ }
+
+ public Result Connect(out KClientSession clientSession)
+ {
+ clientSession = null;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.ResourceLimit != null &&
+ !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ if (!IncrementSessionsCount())
+ {
+ currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+
+ return KernelResult.SessionCountExceeded;
+ }
+
+ KSession session = new KSession(KernelContext, this);
+
+ Result result = _parent.EnqueueIncomingSession(session.ServerSession);
+
+ if (result != Result.Success)
+ {
+ session.ClientSession.DecrementReferenceCount();
+ session.ServerSession.DecrementReferenceCount();
+
+ return result;
+ }
+
+ clientSession = session.ClientSession;
+
+ return result;
+ }
+
+ public Result ConnectLight(out KLightClientSession clientSession)
+ {
+ clientSession = null;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.ResourceLimit != null &&
+ !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ if (!IncrementSessionsCount())
+ {
+ currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+
+ return KernelResult.SessionCountExceeded;
+ }
+
+ KLightSession session = new KLightSession(KernelContext);
+
+ Result result = _parent.EnqueueIncomingLightSession(session.ServerSession);
+
+ if (result != Result.Success)
+ {
+ session.ClientSession.DecrementReferenceCount();
+ session.ServerSession.DecrementReferenceCount();
+
+ return result;
+ }
+
+ clientSession = session.ClientSession;
+
+ return result;
+ }
+
+ private bool IncrementSessionsCount()
+ {
+ while (true)
+ {
+ int currentCount = _sessionsCount;
+
+ if (currentCount < _maxSessions)
+ {
+ if (Interlocked.CompareExchange(ref _sessionsCount, currentCount + 1, currentCount) == currentCount)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public void Disconnect()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ SignalIfMaximumReached(Interlocked.Decrement(ref _sessionsCount));
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ private void SignalIfMaximumReached(int value)
+ {
+ if (value == _maxSessions)
+ {
+ Signal();
+ }
+ }
+
+ public new static Result RemoveName(KernelContext context, string name)
+ {
+ KAutoObject foundObj = FindNamedObject(context, name);
+
+ if (!(foundObj is KClientPort))
+ {
+ return KernelResult.NotFound;
+ }
+
+ return KAutoObject.RemoveName(context, name);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
new file mode 100644
index 00000000..a24bcc31
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
@@ -0,0 +1,84 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KClientSession : KSynchronizationObject
+ {
+ public KProcess CreatorProcess { get; }
+
+ private KSession _parent;
+
+ public ChannelState State { get; set; }
+
+ public KClientPort ParentPort { get; }
+
+ public KClientSession(KernelContext context, KSession parent, KClientPort parentPort) : base(context)
+ {
+ _parent = parent;
+ ParentPort = parentPort;
+
+ parentPort?.IncrementReferenceCount();
+
+ State = ChannelState.Open;
+
+ CreatorProcess = KernelStatic.GetCurrentProcess();
+ CreatorProcess.IncrementReferenceCount();
+ }
+
+ public Result SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize);
+
+ KernelContext.CriticalSection.Enter();
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = Result.Success;
+
+ Result result = _parent.ServerSession.EnqueueRequest(request);
+
+ KernelContext.CriticalSection.Leave();
+
+ if (result == Result.Success)
+ {
+ result = currentThread.ObjSyncResult;
+ }
+
+ return result;
+ }
+
+ public Result SendAsyncRequest(KWritableEvent asyncEvent, ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
+
+ KernelContext.CriticalSection.Enter();
+
+ Result result = _parent.ServerSession.EnqueueRequest(request);
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public void DisconnectFromPort()
+ {
+ if (ParentPort != null)
+ {
+ ParentPort.Disconnect();
+ ParentPort.DecrementReferenceCount();
+ }
+ }
+
+ protected override void Destroy()
+ {
+ _parent.DisconnectClient();
+ _parent.DecrementReferenceCount();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs
new file mode 100644
index 00000000..27a9732b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KLightClientSession : KAutoObject
+ {
+ private readonly KLightSession _parent;
+
+ public KLightClientSession(KernelContext context, KLightSession parent) : base(context)
+ {
+ _parent = parent;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs
new file mode 100644
index 00000000..0edbba6c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KLightServerSession : KAutoObject
+ {
+ private readonly KLightSession _parent;
+
+ public KLightServerSession(KernelContext context, KLightSession parent) : base(context)
+ {
+ _parent = parent;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs
new file mode 100644
index 00000000..3abb1ab0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs
@@ -0,0 +1,16 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KLightSession : KAutoObject
+ {
+ public KLightServerSession ServerSession { get; }
+ public KLightClientSession ClientSession { get; }
+
+ public KLightSession(KernelContext context) : base(context)
+ {
+ ServerSession = new KLightServerSession(context, this);
+ ClientSession = new KLightClientSession(context, this);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs
new file mode 100644
index 00000000..93f0f34c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KPort : KAutoObject
+ {
+ public KServerPort ServerPort { get; }
+ public KClientPort ClientPort { get; }
+
+ private string _name;
+
+ private ChannelState _state;
+
+ public bool IsLight { get; private set; }
+
+ public KPort(KernelContext context, int maxSessions, bool isLight, string name) : base(context)
+ {
+ ServerPort = new KServerPort(context, this);
+ ClientPort = new KClientPort(context, this, maxSessions);
+
+ IsLight = isLight;
+ _name = name;
+
+ _state = ChannelState.Open;
+ }
+
+ public Result EnqueueIncomingSession(KServerSession session)
+ {
+ Result result;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (_state == ChannelState.Open)
+ {
+ ServerPort.EnqueueIncomingSession(session);
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.PortClosed;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public Result EnqueueIncomingLightSession(KLightServerSession session)
+ {
+ Result result;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (_state == ChannelState.Open)
+ {
+ ServerPort.EnqueueIncomingLightSession(session);
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.PortClosed;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs
new file mode 100644
index 00000000..21a3919c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs
@@ -0,0 +1,87 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KServerPort : KSynchronizationObject
+ {
+ private readonly LinkedList<KServerSession> _incomingConnections;
+ private readonly LinkedList<KLightServerSession> _lightIncomingConnections;
+
+ private readonly KPort _parent;
+
+ public bool IsLight => _parent.IsLight;
+
+ public KServerPort(KernelContext context, KPort parent) : base(context)
+ {
+ _parent = parent;
+
+ _incomingConnections = new LinkedList<KServerSession>();
+ _lightIncomingConnections = new LinkedList<KLightServerSession>();
+ }
+
+ public void EnqueueIncomingSession(KServerSession session)
+ {
+ AcceptIncomingConnection(_incomingConnections, session);
+ }
+
+ public void EnqueueIncomingLightSession(KLightServerSession session)
+ {
+ AcceptIncomingConnection(_lightIncomingConnections, session);
+ }
+
+ private void AcceptIncomingConnection<T>(LinkedList<T> list, T session)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ list.AddLast(session);
+
+ if (list.Count == 1)
+ {
+ Signal();
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public KServerSession AcceptIncomingConnection()
+ {
+ return AcceptIncomingConnection(_incomingConnections);
+ }
+
+ public KLightServerSession AcceptIncomingLightConnection()
+ {
+ return AcceptIncomingConnection(_lightIncomingConnections);
+ }
+
+ private T AcceptIncomingConnection<T>(LinkedList<T> list)
+ {
+ T session = default;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (list.Count != 0)
+ {
+ session = list.First.Value;
+
+ list.RemoveFirst();
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return session;
+ }
+
+ public override bool IsSignaled()
+ {
+ if (_parent.IsLight)
+ {
+ return _lightIncomingConnections.Count != 0;
+ }
+ else
+ {
+ return _incomingConnections.Count != 0;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
new file mode 100644
index 00000000..86469c03
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
@@ -0,0 +1,1246 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KServerSession : KSynchronizationObject
+ {
+ private static readonly MemoryState[] IpcMemoryStates = new MemoryState[]
+ {
+ MemoryState.IpcBuffer3,
+ MemoryState.IpcBuffer0,
+ MemoryState.IpcBuffer1,
+ (MemoryState)0xfffce5d4 //This is invalid, shouldn't be accessed.
+ };
+
+ private readonly struct Message
+ {
+ public ulong Address { get; }
+ public ulong Size { get; }
+ public bool IsCustom { get; }
+
+ public Message(KThread thread, ulong customCmdBuffAddress, ulong customCmdBuffSize)
+ {
+ IsCustom = customCmdBuffAddress != 0;
+
+ if (IsCustom)
+ {
+ Address = customCmdBuffAddress;
+ Size = customCmdBuffSize;
+ }
+ else
+ {
+ Address = thread.TlsAddress;
+ Size = 0x100;
+ }
+ }
+
+ public Message(KSessionRequest request) : this(
+ request.ClientThread,
+ request.CustomCmdBuffAddr,
+ request.CustomCmdBuffSize) { }
+ }
+
+ private readonly struct MessageHeader
+ {
+ public uint Word0 { get; }
+ public uint Word1 { get; }
+ public uint Word2 { get; }
+
+ public uint PointerBuffersCount { get; }
+ public uint SendBuffersCount { get; }
+ public uint ReceiveBuffersCount { get; }
+ public uint ExchangeBuffersCount { get; }
+
+ public uint RawDataSizeInWords { get; }
+
+ public uint ReceiveListType { get; }
+
+ public uint MessageSizeInWords { get; }
+ public uint ReceiveListOffsetInWords { get; }
+ public uint ReceiveListOffset { get; }
+
+ public bool HasHandles { get; }
+
+ public bool HasPid { get; }
+
+ public uint CopyHandlesCount { get; }
+ public uint MoveHandlesCount { get; }
+
+ public MessageHeader(uint word0, uint word1, uint word2)
+ {
+ Word0 = word0;
+ Word1 = word1;
+ Word2 = word2;
+
+ HasHandles = word1 >> 31 != 0;
+
+ uint handleDescSizeInWords = 0;
+
+ if (HasHandles)
+ {
+ uint pidSize = (word2 & 1) * 8;
+
+ HasPid = pidSize != 0;
+
+ CopyHandlesCount = (word2 >> 1) & 0xf;
+ MoveHandlesCount = (word2 >> 5) & 0xf;
+
+ handleDescSizeInWords = (pidSize + CopyHandlesCount * 4 + MoveHandlesCount * 4) / 4;
+ }
+ else
+ {
+ HasPid = false;
+
+ CopyHandlesCount = 0;
+ MoveHandlesCount = 0;
+ }
+
+ PointerBuffersCount = (word0 >> 16) & 0xf;
+ SendBuffersCount = (word0 >> 20) & 0xf;
+ ReceiveBuffersCount = (word0 >> 24) & 0xf;
+ ExchangeBuffersCount = word0 >> 28;
+
+ uint pointerDescSizeInWords = PointerBuffersCount * 2;
+ uint sendDescSizeInWords = SendBuffersCount * 3;
+ uint receiveDescSizeInWords = ReceiveBuffersCount * 3;
+ uint exchangeDescSizeInWords = ExchangeBuffersCount * 3;
+
+ RawDataSizeInWords = word1 & 0x3ff;
+
+ ReceiveListType = (word1 >> 10) & 0xf;
+
+ ReceiveListOffsetInWords = (word1 >> 20) & 0x7ff;
+
+ uint paddingSizeInWords = HasHandles ? 3u : 2u;
+
+ MessageSizeInWords = pointerDescSizeInWords +
+ sendDescSizeInWords +
+ receiveDescSizeInWords +
+ exchangeDescSizeInWords +
+ RawDataSizeInWords +
+ paddingSizeInWords +
+ handleDescSizeInWords;
+
+ if (ReceiveListOffsetInWords == 0)
+ {
+ ReceiveListOffsetInWords = MessageSizeInWords;
+ }
+
+ ReceiveListOffset = ReceiveListOffsetInWords * 4;
+ }
+ }
+
+ private struct PointerBufferDesc
+ {
+ public uint ReceiveIndex { get; }
+
+ public uint BufferSize { get; }
+ public ulong BufferAddress { get; set; }
+
+ public PointerBufferDesc(ulong dword)
+ {
+ ReceiveIndex = (uint)dword & 0xf;
+ BufferSize = (uint)dword >> 16;
+
+ BufferAddress = (dword >> 2) & 0x70;
+ BufferAddress |= (dword >> 12) & 0xf;
+
+ BufferAddress = (BufferAddress << 32) | (dword >> 32);
+ }
+
+ public ulong Pack()
+ {
+ ulong dword = (ReceiveIndex & 0xf) | ((BufferSize & 0xffff) << 16);
+
+ dword |= BufferAddress << 32;
+ dword |= (BufferAddress >> 20) & 0xf000;
+ dword |= (BufferAddress >> 30) & 0xffc0;
+
+ return dword;
+ }
+ }
+
+ private KSession _parent;
+
+ private LinkedList<KSessionRequest> _requests;
+
+ private KSessionRequest _activeRequest;
+
+ public KServerSession(KernelContext context, KSession parent) : base(context)
+ {
+ _parent = parent;
+
+ _requests = new LinkedList<KSessionRequest>();
+ }
+
+ public Result EnqueueRequest(KSessionRequest request)
+ {
+ if (_parent.ClientSession.State != ChannelState.Open)
+ {
+ return KernelResult.PortRemoteClosed;
+ }
+
+ if (request.AsyncEvent == null)
+ {
+ if (request.ClientThread.TerminationRequested)
+ {
+ return KernelResult.ThreadTerminating;
+ }
+
+ request.ClientThread.Reschedule(ThreadSchedState.Paused);
+ }
+
+ _requests.AddLast(request);
+
+ if (_requests.Count == 1)
+ {
+ Signal();
+ }
+
+ return Result.Success;
+ }
+
+ public Result Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+ {
+ KThread serverThread = KernelStatic.GetCurrentThread();
+ KProcess serverProcess = serverThread.Owner;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (_parent.ClientSession.State != ChannelState.Open)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.PortRemoteClosed;
+ }
+
+ if (_activeRequest != null || !DequeueRequest(out KSessionRequest request))
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.NotFound;
+ }
+
+ if (request.ClientThread == null)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.PortRemoteClosed;
+ }
+
+ KThread clientThread = request.ClientThread;
+ KProcess clientProcess = clientThread.Owner;
+
+ KernelContext.CriticalSection.Leave();
+
+ _activeRequest = request;
+
+ request.ServerProcess = serverProcess;
+
+ Message clientMsg = new Message(request);
+ Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize);
+
+ MessageHeader clientHeader = GetClientMessageHeader(clientProcess, clientMsg);
+ MessageHeader serverHeader = GetServerMessageHeader(serverMsg);
+
+ Result serverResult = KernelResult.NotFound;
+ Result clientResult = Result.Success;
+
+ void CleanUpForError()
+ {
+ if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == Result.Success)
+ {
+ request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager);
+ }
+
+ CloseAllHandles(serverMsg, clientHeader, serverProcess);
+
+ KernelContext.CriticalSection.Enter();
+
+ _activeRequest = null;
+
+ if (_requests.Count != 0)
+ {
+ Signal();
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ WakeClientThread(request, clientResult);
+ }
+
+ if (clientHeader.ReceiveListType < 2 &&
+ clientHeader.ReceiveListOffset > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+ else if (clientHeader.ReceiveListType == 2 &&
+ clientHeader.ReceiveListOffset + 8 > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+ else if (clientHeader.ReceiveListType > 2 &&
+ clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ if (clientHeader.MessageSizeInWords * 4 > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.CmdBufferTooSmall;
+ }
+
+ ulong[] receiveList = GetReceiveList(
+ serverProcess,
+ serverMsg,
+ serverHeader.ReceiveListType,
+ serverHeader.ReceiveListOffset);
+
+ serverProcess.CpuMemory.Write(serverMsg.Address + 0, clientHeader.Word0);
+ serverProcess.CpuMemory.Write(serverMsg.Address + 4, clientHeader.Word1);
+
+ uint offset;
+
+ // Copy handles.
+ if (clientHeader.HasHandles)
+ {
+ if (clientHeader.MoveHandlesCount != 0)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ serverProcess.CpuMemory.Write(serverMsg.Address + 8, clientHeader.Word2);
+
+ offset = 3;
+
+ if (clientHeader.HasPid)
+ {
+ serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, clientProcess.Pid);
+
+ offset += 2;
+ }
+
+ for (int index = 0; index < clientHeader.CopyHandlesCount; index++)
+ {
+ int newHandle = 0;
+ int handle = clientProcess.CpuMemory.Read<int>(clientMsg.Address + offset * 4);
+
+ if (clientResult == Result.Success && handle != 0)
+ {
+ clientResult = GetCopyObjectHandle(clientThread, serverProcess, handle, out newHandle);
+ }
+
+ serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, newHandle);
+
+ offset++;
+ }
+
+ for (int index = 0; index < clientHeader.MoveHandlesCount; index++)
+ {
+ int newHandle = 0;
+ int handle = clientProcess.CpuMemory.Read<int>(clientMsg.Address + offset * 4);
+
+ if (handle != 0)
+ {
+ if (clientResult == Result.Success)
+ {
+ clientResult = GetMoveObjectHandle(clientProcess, serverProcess, handle, out newHandle);
+ }
+ else
+ {
+ clientProcess.HandleTable.CloseHandle(handle);
+ }
+ }
+
+ serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, newHandle);
+
+ offset++;
+ }
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+ }
+ else
+ {
+ offset = 2;
+ }
+
+ // Copy pointer/receive list buffers.
+ uint recvListDstOffset = 0;
+
+ for (int index = 0; index < clientHeader.PointerBuffersCount; index++)
+ {
+ ulong pointerDesc = clientProcess.CpuMemory.Read<ulong>(clientMsg.Address + offset * 4);
+
+ PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
+
+ if (descriptor.BufferSize != 0)
+ {
+ clientResult = GetReceiveListAddress(
+ descriptor,
+ serverMsg,
+ serverHeader.ReceiveListType,
+ clientHeader.MessageSizeInWords,
+ receiveList,
+ ref recvListDstOffset,
+ out ulong recvListBufferAddress);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+
+ clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess(
+ recvListBufferAddress,
+ descriptor.BufferSize,
+ descriptor.BufferAddress,
+ MemoryState.IsPoolAllocated,
+ MemoryState.IsPoolAllocated,
+ KMemoryPermission.Read,
+ MemoryAttribute.Uncached,
+ MemoryAttribute.None);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+
+ descriptor.BufferAddress = recvListBufferAddress;
+ }
+ else
+ {
+ descriptor.BufferAddress = 0;
+ }
+
+ serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, descriptor.Pack());
+
+ offset += 2;
+ }
+
+ // Copy send, receive and exchange buffers.
+ uint totalBuffersCount =
+ clientHeader.SendBuffersCount +
+ clientHeader.ReceiveBuffersCount +
+ clientHeader.ExchangeBuffersCount;
+
+ for (int index = 0; index < totalBuffersCount; index++)
+ {
+ ulong clientDescAddress = clientMsg.Address + offset * 4;
+
+ uint descWord0 = clientProcess.CpuMemory.Read<uint>(clientDescAddress + 0);
+ uint descWord1 = clientProcess.CpuMemory.Read<uint>(clientDescAddress + 4);
+ uint descWord2 = clientProcess.CpuMemory.Read<uint>(clientDescAddress + 8);
+
+ bool isSendDesc = index < clientHeader.SendBuffersCount;
+ bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount;
+
+ bool notReceiveDesc = isSendDesc || isExchangeDesc;
+ bool isReceiveDesc = !notReceiveDesc;
+
+ KMemoryPermission permission = index >= clientHeader.SendBuffersCount
+ ? KMemoryPermission.ReadAndWrite
+ : KMemoryPermission.Read;
+
+ uint sizeHigh4 = (descWord2 >> 24) & 0xf;
+
+ ulong bufferSize = descWord0 | (ulong)sizeHigh4 << 32;
+
+ ulong dstAddress = 0;
+
+ if (bufferSize != 0)
+ {
+ ulong bufferAddress;
+
+ bufferAddress = descWord2 >> 28;
+ bufferAddress |= ((descWord2 >> 2) & 7) << 4;
+
+ bufferAddress = (bufferAddress << 32) | descWord1;
+
+ MemoryState state = IpcMemoryStates[(descWord2 + 1) & 3];
+
+ clientResult = serverProcess.MemoryManager.MapBufferFromClientProcess(
+ bufferSize,
+ bufferAddress,
+ clientProcess.MemoryManager,
+ permission,
+ state,
+ notReceiveDesc,
+ out dstAddress);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+
+ if (isSendDesc)
+ {
+ clientResult = request.BufferDescriptorTable.AddSendBuffer(bufferAddress, dstAddress, bufferSize, state);
+ }
+ else if (isReceiveDesc)
+ {
+ clientResult = request.BufferDescriptorTable.AddReceiveBuffer(bufferAddress, dstAddress, bufferSize, state);
+ }
+ else /* if (isExchangeDesc) */
+ {
+ clientResult = request.BufferDescriptorTable.AddExchangeBuffer(bufferAddress, dstAddress, bufferSize, state);
+ }
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+ }
+
+ descWord1 = (uint)dstAddress;
+
+ descWord2 &= 3;
+
+ descWord2 |= sizeHigh4 << 24;
+
+ descWord2 |= (uint)(dstAddress >> 34) & 0x3ffffffc;
+ descWord2 |= (uint)(dstAddress >> 4) & 0xf0000000;
+
+ ulong serverDescAddress = serverMsg.Address + offset * 4;
+
+ serverProcess.CpuMemory.Write(serverDescAddress + 0, descWord0);
+ serverProcess.CpuMemory.Write(serverDescAddress + 4, descWord1);
+ serverProcess.CpuMemory.Write(serverDescAddress + 8, descWord2);
+
+ offset += 3;
+ }
+
+ // Copy raw data.
+ if (clientHeader.RawDataSizeInWords != 0)
+ {
+ ulong copySrc = clientMsg.Address + offset * 4;
+ ulong copyDst = serverMsg.Address + offset * 4;
+
+ ulong copySize = clientHeader.RawDataSizeInWords * 4;
+
+ if (serverMsg.IsCustom || clientMsg.IsCustom)
+ {
+ KMemoryPermission permission = clientMsg.IsCustom
+ ? KMemoryPermission.None
+ : KMemoryPermission.Read;
+
+ clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess(
+ copyDst,
+ copySize,
+ copySrc,
+ MemoryState.IsPoolAllocated,
+ MemoryState.IsPoolAllocated,
+ permission,
+ MemoryAttribute.Uncached,
+ MemoryAttribute.None);
+ }
+ else
+ {
+ serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetSpan(copySrc, (int)copySize));
+ }
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+ {
+ KThread serverThread = KernelStatic.GetCurrentThread();
+ KProcess serverProcess = serverThread.Owner;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (_activeRequest == null)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ KSessionRequest request = _activeRequest;
+
+ _activeRequest = null;
+
+ if (_requests.Count != 0)
+ {
+ Signal();
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ KThread clientThread = request.ClientThread;
+ KProcess clientProcess = clientThread.Owner;
+
+ Message clientMsg = new Message(request);
+ Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize);
+
+ MessageHeader clientHeader = GetClientMessageHeader(clientProcess, clientMsg);
+ MessageHeader serverHeader = GetServerMessageHeader(serverMsg);
+
+ Result clientResult = Result.Success;
+ Result serverResult = Result.Success;
+
+ void CleanUpForError()
+ {
+ CloseAllHandles(clientMsg, serverHeader, clientProcess);
+
+ FinishRequest(request, clientResult);
+ }
+
+ if (clientHeader.ReceiveListType < 2 &&
+ clientHeader.ReceiveListOffset > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+ else if (clientHeader.ReceiveListType == 2 &&
+ clientHeader.ReceiveListOffset + 8 > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+ else if (clientHeader.ReceiveListType > 2 &&
+ clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ if (serverHeader.MessageSizeInWords * 4 > clientMsg.Size)
+ {
+ CleanUpForError();
+
+ return KernelResult.CmdBufferTooSmall;
+ }
+
+ if (serverHeader.SendBuffersCount != 0 ||
+ serverHeader.ReceiveBuffersCount != 0 ||
+ serverHeader.ExchangeBuffersCount != 0)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidCombination;
+ }
+
+ // Read receive list.
+ ulong[] receiveList = GetReceiveList(
+ clientProcess,
+ clientMsg,
+ clientHeader.ReceiveListType,
+ clientHeader.ReceiveListOffset);
+
+ // Copy receive and exchange buffers.
+ clientResult = request.BufferDescriptorTable.CopyBuffersToClient(clientProcess.MemoryManager);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+
+ // Copy header.
+ clientProcess.CpuMemory.Write(clientMsg.Address + 0, serverHeader.Word0);
+ clientProcess.CpuMemory.Write(clientMsg.Address + 4, serverHeader.Word1);
+
+ // Copy handles.
+ uint offset;
+
+ if (serverHeader.HasHandles)
+ {
+ offset = 3;
+
+ clientProcess.CpuMemory.Write(clientMsg.Address + 8, serverHeader.Word2);
+
+ if (serverHeader.HasPid)
+ {
+ clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, serverProcess.Pid);
+
+ offset += 2;
+ }
+
+ for (int index = 0; index < serverHeader.CopyHandlesCount; index++)
+ {
+ int newHandle = 0;
+
+ int handle = serverProcess.CpuMemory.Read<int>(serverMsg.Address + offset * 4);
+
+ if (handle != 0)
+ {
+ GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle);
+ }
+
+ clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, newHandle);
+
+ offset++;
+ }
+
+ for (int index = 0; index < serverHeader.MoveHandlesCount; index++)
+ {
+ int newHandle = 0;
+
+ int handle = serverProcess.CpuMemory.Read<int>(serverMsg.Address + offset * 4);
+
+ if (handle != 0)
+ {
+ if (clientResult == Result.Success)
+ {
+ clientResult = GetMoveObjectHandle(serverProcess, clientProcess, handle, out newHandle);
+ }
+ else
+ {
+ serverProcess.HandleTable.CloseHandle(handle);
+ }
+ }
+
+ clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, newHandle);
+
+ offset++;
+ }
+ }
+ else
+ {
+ offset = 2;
+ }
+
+ // Copy pointer/receive list buffers.
+ uint recvListDstOffset = 0;
+
+ for (int index = 0; index < serverHeader.PointerBuffersCount; index++)
+ {
+ ulong pointerDesc = serverProcess.CpuMemory.Read<ulong>(serverMsg.Address + offset * 4);
+
+ PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
+
+ ulong recvListBufferAddress = 0;
+
+ if (descriptor.BufferSize != 0)
+ {
+ clientResult = GetReceiveListAddress(
+ descriptor,
+ clientMsg,
+ clientHeader.ReceiveListType,
+ serverHeader.MessageSizeInWords,
+ receiveList,
+ ref recvListDstOffset,
+ out recvListBufferAddress);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+
+ clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess(
+ recvListBufferAddress,
+ descriptor.BufferSize,
+ MemoryState.IsPoolAllocated,
+ MemoryState.IsPoolAllocated,
+ KMemoryPermission.Read,
+ MemoryAttribute.Uncached,
+ MemoryAttribute.None,
+ descriptor.BufferAddress);
+
+ if (clientResult != Result.Success)
+ {
+ CleanUpForError();
+
+ return serverResult;
+ }
+ }
+
+ ulong dstDescAddress = clientMsg.Address + offset * 4;
+
+ ulong clientPointerDesc =
+ (recvListBufferAddress << 32) |
+ ((recvListBufferAddress >> 20) & 0xf000) |
+ ((recvListBufferAddress >> 30) & 0xffc0);
+
+ clientPointerDesc |= pointerDesc & 0xffff000f;
+
+ clientProcess.CpuMemory.Write(dstDescAddress + 0, clientPointerDesc);
+
+ offset += 2;
+ }
+
+ // Set send, receive and exchange buffer descriptors to zero.
+ uint totalBuffersCount =
+ serverHeader.SendBuffersCount +
+ serverHeader.ReceiveBuffersCount +
+ serverHeader.ExchangeBuffersCount;
+
+ for (int index = 0; index < totalBuffersCount; index++)
+ {
+ ulong dstDescAddress = clientMsg.Address + offset * 4;
+
+ clientProcess.CpuMemory.Write(dstDescAddress + 0, 0);
+ clientProcess.CpuMemory.Write(dstDescAddress + 4, 0);
+ clientProcess.CpuMemory.Write(dstDescAddress + 8, 0);
+
+ offset += 3;
+ }
+
+ // Copy raw data.
+ if (serverHeader.RawDataSizeInWords != 0)
+ {
+ ulong copyDst = clientMsg.Address + offset * 4;
+ ulong copySrc = serverMsg.Address + offset * 4;
+
+ ulong copySize = serverHeader.RawDataSizeInWords * 4;
+
+ if (serverMsg.IsCustom || clientMsg.IsCustom)
+ {
+ KMemoryPermission permission = clientMsg.IsCustom
+ ? KMemoryPermission.None
+ : KMemoryPermission.Read;
+
+ clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess(
+ copyDst,
+ copySize,
+ MemoryState.IsPoolAllocated,
+ MemoryState.IsPoolAllocated,
+ permission,
+ MemoryAttribute.Uncached,
+ MemoryAttribute.None,
+ copySrc);
+ }
+ else
+ {
+ clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetSpan(copySrc, (int)copySize));
+ }
+ }
+
+ // Unmap buffers from server.
+ FinishRequest(request, clientResult);
+
+ return serverResult;
+ }
+
+ private MessageHeader GetClientMessageHeader(KProcess clientProcess, Message clientMsg)
+ {
+ uint word0 = clientProcess.CpuMemory.Read<uint>(clientMsg.Address + 0);
+ uint word1 = clientProcess.CpuMemory.Read<uint>(clientMsg.Address + 4);
+ uint word2 = clientProcess.CpuMemory.Read<uint>(clientMsg.Address + 8);
+
+ return new MessageHeader(word0, word1, word2);
+ }
+
+ private MessageHeader GetServerMessageHeader(Message serverMsg)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ uint word0 = currentProcess.CpuMemory.Read<uint>(serverMsg.Address + 0);
+ uint word1 = currentProcess.CpuMemory.Read<uint>(serverMsg.Address + 4);
+ uint word2 = currentProcess.CpuMemory.Read<uint>(serverMsg.Address + 8);
+
+ return new MessageHeader(word0, word1, word2);
+ }
+
+ private Result GetCopyObjectHandle(KThread srcThread, KProcess dstProcess, int srcHandle, out int dstHandle)
+ {
+ dstHandle = 0;
+
+ KProcess srcProcess = srcThread.Owner;
+
+ KAutoObject obj;
+
+ if (srcHandle == KHandleTable.SelfProcessHandle)
+ {
+ obj = srcProcess;
+ }
+ else if (srcHandle == KHandleTable.SelfThreadHandle)
+ {
+ obj = srcThread;
+ }
+ else
+ {
+ obj = srcProcess.HandleTable.GetObject<KAutoObject>(srcHandle);
+ }
+
+ if (obj != null)
+ {
+ return dstProcess.HandleTable.GenerateHandle(obj, out dstHandle);
+ }
+ else
+ {
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ private Result GetMoveObjectHandle(KProcess srcProcess, KProcess dstProcess, int srcHandle, out int dstHandle)
+ {
+ dstHandle = 0;
+
+ KAutoObject obj = srcProcess.HandleTable.GetObject<KAutoObject>(srcHandle);
+
+ if (obj != null)
+ {
+ Result result = dstProcess.HandleTable.GenerateHandle(obj, out dstHandle);
+
+ srcProcess.HandleTable.CloseHandle(srcHandle);
+
+ return result;
+ }
+ else
+ {
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ private ulong[] GetReceiveList(KProcess ownerProcess, Message message, uint recvListType, uint recvListOffset)
+ {
+ int recvListSize = 0;
+
+ if (recvListType >= 3)
+ {
+ recvListSize = (int)recvListType - 2;
+ }
+ else if (recvListType == 2)
+ {
+ recvListSize = 1;
+ }
+
+ ulong[] receiveList = new ulong[recvListSize];
+
+ ulong recvListAddress = message.Address + recvListOffset;
+
+ for (int index = 0; index < recvListSize; index++)
+ {
+ receiveList[index] = ownerProcess.CpuMemory.Read<ulong>(recvListAddress + (ulong)index * 8);
+ }
+
+ return receiveList;
+ }
+
+ private Result GetReceiveListAddress(
+ PointerBufferDesc descriptor,
+ Message message,
+ uint recvListType,
+ uint messageSizeInWords,
+ ulong[] receiveList,
+ ref uint dstOffset,
+ out ulong address)
+ {
+ ulong recvListBufferAddress = address = 0;
+
+ if (recvListType == 0)
+ {
+ return KernelResult.OutOfResource;
+ }
+ else if (recvListType == 1 || recvListType == 2)
+ {
+ ulong recvListBaseAddr;
+ ulong recvListEndAddr;
+
+ if (recvListType == 1)
+ {
+ recvListBaseAddr = message.Address + messageSizeInWords * 4;
+ recvListEndAddr = message.Address + message.Size;
+ }
+ else /* if (recvListType == 2) */
+ {
+ ulong packed = receiveList[0];
+
+ recvListBaseAddr = packed & 0x7fffffffff;
+
+ uint size = (uint)(packed >> 48);
+
+ if (size == 0)
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ recvListEndAddr = recvListBaseAddr + size;
+ }
+
+ recvListBufferAddress = BitUtils.AlignUp<ulong>(recvListBaseAddr + dstOffset, 0x10);
+
+ ulong endAddress = recvListBufferAddress + descriptor.BufferSize;
+
+ dstOffset = (uint)endAddress - (uint)recvListBaseAddr;
+
+ if (recvListBufferAddress + descriptor.BufferSize <= recvListBufferAddress ||
+ recvListBufferAddress + descriptor.BufferSize > recvListEndAddr)
+ {
+ return KernelResult.OutOfResource;
+ }
+ }
+ else /* if (recvListType > 2) */
+ {
+ if (descriptor.ReceiveIndex >= receiveList.Length)
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong packed = receiveList[descriptor.ReceiveIndex];
+
+ recvListBufferAddress = packed & 0x7fffffffff;
+
+ uint size = (uint)(packed >> 48);
+
+ if (recvListBufferAddress == 0 || size == 0 || size < descriptor.BufferSize)
+ {
+ return KernelResult.OutOfResource;
+ }
+ }
+
+ address = recvListBufferAddress;
+
+ return Result.Success;
+ }
+
+ private void CloseAllHandles(Message message, MessageHeader header, KProcess process)
+ {
+ if (header.HasHandles)
+ {
+ uint totalHandeslCount = header.CopyHandlesCount + header.MoveHandlesCount;
+
+ uint offset = 3;
+
+ if (header.HasPid)
+ {
+ process.CpuMemory.Write(message.Address + offset * 4, 0L);
+
+ offset += 2;
+ }
+
+ for (int index = 0; index < totalHandeslCount; index++)
+ {
+ int handle = process.CpuMemory.Read<int>(message.Address + offset * 4);
+
+ if (handle != 0)
+ {
+ process.HandleTable.CloseHandle(handle);
+
+ process.CpuMemory.Write(message.Address + offset * 4, 0);
+ }
+
+ offset++;
+ }
+ }
+ }
+
+ public override bool IsSignaled()
+ {
+ if (_parent.ClientSession.State != ChannelState.Open)
+ {
+ return true;
+ }
+
+ return _requests.Count != 0 && _activeRequest == null;
+ }
+
+ protected override void Destroy()
+ {
+ _parent.DisconnectServer();
+
+ CancelAllRequestsServerDisconnected();
+
+ _parent.DecrementReferenceCount();
+ }
+
+ private void CancelAllRequestsServerDisconnected()
+ {
+ foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
+ {
+ FinishRequest(request, KernelResult.PortRemoteClosed);
+ }
+ }
+
+ public void CancelAllRequestsClientDisconnected()
+ {
+ foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
+ {
+ if (request.ClientThread.TerminationRequested)
+ {
+ continue;
+ }
+
+ // Client sessions can only be disconnected on async requests (because
+ // the client would be otherwise blocked waiting for the response), so
+ // we only need to handle the async case here.
+ if (request.AsyncEvent != null)
+ {
+ SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
+ }
+ }
+
+ WakeServerThreads(KernelResult.PortRemoteClosed);
+ }
+
+ private IEnumerable<KSessionRequest> IterateWithRemovalOfAllRequests()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (_activeRequest != null)
+ {
+ KSessionRequest request = _activeRequest;
+
+ _activeRequest = null;
+
+ KernelContext.CriticalSection.Leave();
+
+ yield return request;
+ }
+ else
+ {
+ KernelContext.CriticalSection.Leave();
+ }
+
+ while (DequeueRequest(out KSessionRequest request))
+ {
+ yield return request;
+ }
+ }
+
+ private bool DequeueRequest(out KSessionRequest request)
+ {
+ request = null;
+
+ KernelContext.CriticalSection.Enter();
+
+ bool hasRequest = _requests.First != null;
+
+ if (hasRequest)
+ {
+ request = _requests.First.Value;
+
+ _requests.RemoveFirst();
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return hasRequest;
+ }
+
+ private void FinishRequest(KSessionRequest request, Result result)
+ {
+ KProcess clientProcess = request.ClientThread.Owner;
+ KProcess serverProcess = request.ServerProcess;
+
+ Result unmapResult = Result.Success;
+
+ if (serverProcess != null)
+ {
+ unmapResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager);
+ }
+
+ if (unmapResult == Result.Success)
+ {
+ request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager);
+ }
+
+ WakeClientThread(request, result);
+ }
+
+ private void WakeClientThread(KSessionRequest request, Result result)
+ {
+ // Wait client thread waiting for a response for the given request.
+ if (request.AsyncEvent != null)
+ {
+ SendResultToAsyncRequestClient(request, result);
+ }
+ else
+ {
+ KernelContext.CriticalSection.Enter();
+
+ WakeAndSetResult(request.ClientThread, result);
+
+ KernelContext.CriticalSection.Leave();
+ }
+ }
+
+ private void SendResultToAsyncRequestClient(KSessionRequest request, Result result)
+ {
+ KProcess clientProcess = request.ClientThread.Owner;
+
+ if (result != Result.Success)
+ {
+ ulong address = request.CustomCmdBuffAddr;
+
+ clientProcess.CpuMemory.Write<ulong>(address, 0);
+ clientProcess.CpuMemory.Write(address + 8, result.ErrorCode);
+ }
+
+ clientProcess.MemoryManager.UnborrowIpcBuffer(request.CustomCmdBuffAddr, request.CustomCmdBuffSize);
+
+ request.AsyncEvent.Signal();
+ }
+
+ private void WakeServerThreads(Result result)
+ {
+ // Wake all server threads waiting for requests.
+ KernelContext.CriticalSection.Enter();
+
+ foreach (KThread thread in WaitingThreads)
+ {
+ WakeAndSetResult(thread, result, this);
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ private void WakeAndSetResult(KThread thread, Result result, KSynchronizationObject signaledObj = null)
+ {
+ if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+ {
+ thread.SignaledObj = signaledObj;
+ thread.ObjSyncResult = result;
+
+ thread.Reschedule(ThreadSchedState.Running);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs
new file mode 100644
index 00000000..13cf4b51
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs
@@ -0,0 +1,54 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KSession : KAutoObject
+ {
+ public KServerSession ServerSession { get; }
+ public KClientSession ClientSession { get; }
+
+ private bool _hasBeenInitialized;
+
+ public KSession(KernelContext context, KClientPort parentPort = null) : base(context)
+ {
+ IncrementReferenceCount();
+
+ ServerSession = new KServerSession(context, this);
+ ClientSession = new KClientSession(context, this, parentPort);
+
+ _hasBeenInitialized = true;
+ }
+
+ public void DisconnectClient()
+ {
+ if (ClientSession.State == ChannelState.Open)
+ {
+ ClientSession.State = ChannelState.ClientDisconnected;
+
+ ServerSession.CancelAllRequestsClientDisconnected();
+ }
+ }
+
+ public void DisconnectServer()
+ {
+ if (ClientSession.State == ChannelState.Open)
+ {
+ ClientSession.State = ChannelState.ServerDisconnected;
+ }
+ }
+
+ protected override void Destroy()
+ {
+ if (_hasBeenInitialized)
+ {
+ ClientSession.DisconnectFromPort();
+
+ KProcess creatorProcess = ClientSession.CreatorProcess;
+
+ creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+ creatorProcess.DecrementReferenceCount();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
new file mode 100644
index 00000000..31ddfc9c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
@@ -0,0 +1,33 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+ class KSessionRequest
+ {
+ public KBufferDescriptorTable BufferDescriptorTable { get; }
+
+ public KThread ClientThread { get; }
+
+ public KProcess ServerProcess { get; set; }
+
+ public KWritableEvent AsyncEvent { get; }
+
+ public ulong CustomCmdBuffAddr { get; }
+ public ulong CustomCmdBuffSize { get; }
+
+ public KSessionRequest(
+ KThread clientThread,
+ ulong customCmdBuffAddr,
+ ulong customCmdBuffSize,
+ KWritableEvent asyncEvent = null)
+ {
+ ClientThread = clientThread;
+ CustomCmdBuffAddr = customCmdBuffAddr;
+ CustomCmdBuffSize = customCmdBuffSize;
+ AsyncEvent = asyncEvent;
+
+ BufferDescriptorTable = new KBufferDescriptorTable();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs
new file mode 100644
index 00000000..28db750c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class KernelConstants
+ {
+ public const int InitialKipId = 1;
+ public const int InitialProcessId = 0x51;
+
+ public const int SupervisorCallCount = 0xC0;
+
+ public const int MemoryBlockAllocatorSize = 0x2710;
+
+ public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
+ public const ulong UserSlabHeapItemSize = KPageTableBase.PageSize;
+ public const ulong UserSlabHeapSize = 0x3de000;
+
+ public const ulong CounterFrequency = 19200000;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs
new file mode 100644
index 00000000..ccc5c0f0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs
@@ -0,0 +1,160 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KernelContext : IDisposable
+ {
+ public long PrivilegedProcessLowestId { get; set; } = 1;
+ public long PrivilegedProcessHighestId { get; set; } = 8;
+
+ public bool EnableVersionChecks { get; set; }
+
+ public bool KernelInitialized { get; }
+
+ public bool Running { get; private set; }
+
+ public Switch Device { get; }
+ public MemoryBlock Memory { get; }
+ public ITickSource TickSource { get; }
+ public Syscall Syscall { get; }
+ public SyscallHandler SyscallHandler { get; }
+
+ public KResourceLimit ResourceLimit { get; }
+
+ public KMemoryManager MemoryManager { get; }
+
+ public KMemoryBlockSlabManager LargeMemoryBlockSlabManager { get; }
+ public KMemoryBlockSlabManager SmallMemoryBlockSlabManager { get; }
+
+ public KSlabHeap UserSlabHeapPages { get; }
+
+ public KCriticalSection CriticalSection { get; }
+ public KScheduler[] Schedulers { get; }
+ public KPriorityQueue PriorityQueue { get; }
+ public KTimeManager TimeManager { get; }
+ public KSynchronization Synchronization { get; }
+ public KContextIdManager ContextIdManager { get; }
+
+ public ConcurrentDictionary<ulong, KProcess> Processes { get; }
+ public ConcurrentDictionary<string, KAutoObject> AutoObjectNames { get; }
+
+ public bool ThreadReselectionRequested { get; set; }
+
+ private ulong _kipId;
+ private ulong _processId;
+ private ulong _threadUid;
+
+ public KernelContext(
+ ITickSource tickSource,
+ Switch device,
+ MemoryBlock memory,
+ MemorySize memorySize,
+ MemoryArrange memoryArrange)
+ {
+ TickSource = tickSource;
+ Device = device;
+ Memory = memory;
+
+ Running = true;
+
+ Syscall = new Syscall(this);
+
+ SyscallHandler = new SyscallHandler(this);
+
+ ResourceLimit = new KResourceLimit(this);
+
+ KernelInit.InitializeResourceLimit(ResourceLimit, memorySize);
+
+ MemoryManager = new KMemoryManager(memorySize, memoryArrange);
+
+ LargeMemoryBlockSlabManager = new KMemoryBlockSlabManager(KernelConstants.MemoryBlockAllocatorSize * 2);
+ SmallMemoryBlockSlabManager = new KMemoryBlockSlabManager(KernelConstants.MemoryBlockAllocatorSize);
+
+ UserSlabHeapPages = new KSlabHeap(
+ KernelConstants.UserSlabHeapBase,
+ KernelConstants.UserSlabHeapItemSize,
+ KernelConstants.UserSlabHeapSize);
+
+ CommitMemory(KernelConstants.UserSlabHeapBase - DramMemoryMap.DramBase, KernelConstants.UserSlabHeapSize);
+
+ CriticalSection = new KCriticalSection(this);
+ Schedulers = new KScheduler[KScheduler.CpuCoresCount];
+ PriorityQueue = new KPriorityQueue();
+ TimeManager = new KTimeManager(this);
+ Synchronization = new KSynchronization(this);
+ ContextIdManager = new KContextIdManager();
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ Schedulers[core] = new KScheduler(this, core);
+ }
+
+ StartPreemptionThread();
+
+ KernelInitialized = true;
+
+ Processes = new ConcurrentDictionary<ulong, KProcess>();
+ AutoObjectNames = new ConcurrentDictionary<string, KAutoObject>();
+
+ _kipId = KernelConstants.InitialKipId;
+ _processId = KernelConstants.InitialProcessId;
+ }
+
+ private void StartPreemptionThread()
+ {
+ void PreemptionThreadStart()
+ {
+ KScheduler.PreemptionThreadLoop(this);
+ }
+
+ new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start();
+ }
+
+ public void CommitMemory(ulong address, ulong size)
+ {
+ ulong alignment = MemoryBlock.GetPageSize();
+ ulong endAddress = address + size;
+
+ address &= ~(alignment - 1);
+ endAddress = (endAddress + (alignment - 1)) & ~(alignment - 1);
+
+ Memory.Commit(address, endAddress - address);
+ }
+
+ public ulong NewThreadUid()
+ {
+ return Interlocked.Increment(ref _threadUid) - 1;
+ }
+
+ public ulong NewKipId()
+ {
+ return Interlocked.Increment(ref _kipId) - 1;
+ }
+
+ public ulong NewProcessId()
+ {
+ return Interlocked.Increment(ref _processId) - 1;
+ }
+
+ public void Dispose()
+ {
+ Running = false;
+
+ for (int i = 0; i < KScheduler.CpuCoresCount; i++)
+ {
+ Schedulers[i].Dispose();
+ }
+
+ TimeManager.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs
new file mode 100644
index 00000000..c66f4b57
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs
@@ -0,0 +1,73 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class KernelStatic
+ {
+ [ThreadStatic]
+ private static KernelContext Context;
+
+ [ThreadStatic]
+ private static KThread CurrentThread;
+
+ public static Result StartInitialProcess(
+ KernelContext context,
+ ProcessCreationInfo creationInfo,
+ ReadOnlySpan<uint> capabilities,
+ int mainThreadPriority,
+ ThreadStart customThreadStart)
+ {
+ KProcess process = new KProcess(context);
+
+ Result result = process.Initialize(
+ creationInfo,
+ capabilities,
+ context.ResourceLimit,
+ MemoryRegion.Service,
+ null,
+ customThreadStart);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ process.DefaultCpuCore = 3;
+
+ context.Processes.TryAdd(process.Pid, process);
+
+ return process.Start(mainThreadPriority, 0x1000UL);
+ }
+
+ internal static void SetKernelContext(KernelContext context, KThread thread)
+ {
+ Context = context;
+ CurrentThread = thread;
+ }
+
+ internal static KThread GetCurrentThread()
+ {
+ return CurrentThread;
+ }
+
+ internal static KProcess GetCurrentProcess()
+ {
+ return GetCurrentThread().Owner;
+ }
+
+ internal static KProcess GetProcessByPid(ulong pid)
+ {
+ if (Context.Processes.TryGetValue(pid, out KProcess process))
+ {
+ return process;
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs
new file mode 100644
index 00000000..8395c577
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ enum AddressSpaceType
+ {
+ Addr32Bits = 0,
+ Addr36Bits = 1,
+ Addr32BitsNoMap = 2,
+ Addr39Bits = 3
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs
new file mode 100644
index 00000000..4941d5b7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ static class DramMemoryMap
+ {
+ public const ulong DramBase = 0x80000000;
+
+ public const ulong KernelReserveBase = DramBase + 0x60000;
+
+ public const ulong SlabHeapBase = KernelReserveBase + 0x85000;
+ public const ulong SlapHeapSize = 0xa21000;
+ public const ulong SlabHeapEnd = SlabHeapBase + SlapHeapSize;
+
+ public static bool IsHeapPhysicalAddress(ulong address)
+ {
+ return address >= SlabHeapEnd;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs
new file mode 100644
index 00000000..11474e49
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs
@@ -0,0 +1,169 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KCodeMemory : KAutoObject
+ {
+ public KProcess Owner { get; private set; }
+ private readonly KPageList _pageList;
+ private readonly object _lock;
+ private ulong _address;
+ private bool _isOwnerMapped;
+ private bool _isMapped;
+
+ public KCodeMemory(KernelContext context) : base(context)
+ {
+ _pageList = new KPageList();
+ _lock = new object();
+ }
+
+ public Result Initialize(ulong address, ulong size)
+ {
+ Owner = KernelStatic.GetCurrentProcess();
+
+ Result result = Owner.MemoryManager.BorrowCodeMemory(_pageList, address, size);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ Owner.CpuMemory.Fill(address, size, 0xff);
+ Owner.IncrementReferenceCount();
+
+ _address = address;
+ _isMapped = false;
+ _isOwnerMapped = false;
+
+ return Result.Success;
+ }
+
+ public Result Map(ulong address, ulong size, KMemoryPermission perm)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, (ulong)KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ lock (_lock)
+ {
+ if (_isMapped)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ Result result = process.MemoryManager.MapPages(address, _pageList, MemoryState.CodeWritable, KMemoryPermission.ReadAndWrite);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _isMapped = true;
+ }
+
+ return Result.Success;
+ }
+
+ public Result MapToOwner(ulong address, ulong size, KMemoryPermission permission)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, (ulong)KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ lock (_lock)
+ {
+ if (_isOwnerMapped)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ Debug.Assert(permission == KMemoryPermission.Read || permission == KMemoryPermission.ReadAndExecute);
+
+ Result result = Owner.MemoryManager.MapPages(address, _pageList, MemoryState.CodeReadOnly, permission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _isOwnerMapped = true;
+ }
+
+ return Result.Success;
+ }
+
+ public Result Unmap(ulong address, ulong size)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, (ulong)KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ lock (_lock)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ Result result = process.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeWritable);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ Debug.Assert(_isMapped);
+
+ _isMapped = false;
+ }
+
+ return Result.Success;
+ }
+
+ public Result UnmapFromOwner(ulong address, ulong size)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ lock (_lock)
+ {
+ Result result = Owner.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeReadOnly);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ Debug.Assert(_isOwnerMapped);
+
+ _isOwnerMapped = false;
+ }
+
+ return Result.Success;
+ }
+
+ protected override void Destroy()
+ {
+ if (!_isMapped && !_isOwnerMapped)
+ {
+ ulong size = _pageList.GetPagesCount() * KPageTableBase.PageSize;
+
+ if (Owner.MemoryManager.UnborrowCodeMemory(_address, size, _pageList) != Result.Success)
+ {
+ throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
+ }
+ }
+
+ Owner.DecrementReferenceCount();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
new file mode 100644
index 00000000..e082105b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
@@ -0,0 +1,156 @@
+using Ryujinx.Common.Collections;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryBlock : IntrusiveRedBlackTreeNode<KMemoryBlock>, IComparable<KMemoryBlock>, IComparable<ulong>
+ {
+ public ulong BaseAddress { get; private set; }
+ public ulong PagesCount { get; private set; }
+
+ public MemoryState State { get; private set; }
+ public KMemoryPermission Permission { get; private set; }
+ public MemoryAttribute Attribute { get; private set; }
+ public KMemoryPermission SourcePermission { get; private set; }
+
+ public int IpcRefCount { get; private set; }
+ public int DeviceRefCount { get; private set; }
+
+ public KMemoryBlock(
+ ulong baseAddress,
+ ulong pagesCount,
+ MemoryState state,
+ KMemoryPermission permission,
+ MemoryAttribute attribute,
+ int ipcRefCount = 0,
+ int deviceRefCount = 0)
+ {
+ BaseAddress = baseAddress;
+ PagesCount = pagesCount;
+ State = state;
+ Attribute = attribute;
+ Permission = permission;
+ IpcRefCount = ipcRefCount;
+ DeviceRefCount = deviceRefCount;
+ }
+
+ public void SetState(KMemoryPermission permission, MemoryState state, MemoryAttribute attribute)
+ {
+ Permission = permission;
+ State = state;
+ Attribute &= MemoryAttribute.IpcAndDeviceMapped;
+ Attribute |= attribute;
+ }
+
+ public void SetIpcMappingPermission(KMemoryPermission newPermission)
+ {
+ int oldIpcRefCount = IpcRefCount++;
+
+ if ((ushort)IpcRefCount == 0)
+ {
+ throw new InvalidOperationException("IPC reference count increment overflowed.");
+ }
+
+ if (oldIpcRefCount == 0)
+ {
+ SourcePermission = Permission;
+
+ Permission &= ~KMemoryPermission.ReadAndWrite;
+ Permission |= KMemoryPermission.ReadAndWrite & newPermission;
+ }
+
+ Attribute |= MemoryAttribute.IpcMapped;
+ }
+
+ public void RestoreIpcMappingPermission()
+ {
+ int oldIpcRefCount = IpcRefCount--;
+
+ if (oldIpcRefCount == 0)
+ {
+ throw new InvalidOperationException("IPC reference count decrement underflowed.");
+ }
+
+ if (oldIpcRefCount == 1)
+ {
+ Permission = SourcePermission;
+
+ SourcePermission = KMemoryPermission.None;
+
+ Attribute &= ~MemoryAttribute.IpcMapped;
+ }
+ }
+
+ public KMemoryBlock SplitRightAtAddress(ulong address)
+ {
+ ulong leftAddress = BaseAddress;
+
+ ulong leftPagesCount = (address - leftAddress) / KPageTableBase.PageSize;
+
+ BaseAddress = address;
+
+ PagesCount -= leftPagesCount;
+
+ return new KMemoryBlock(
+ leftAddress,
+ leftPagesCount,
+ State,
+ Permission,
+ Attribute,
+ IpcRefCount,
+ DeviceRefCount);
+ }
+
+ public void AddPages(ulong pagesCount)
+ {
+ PagesCount += pagesCount;
+ }
+
+ public KMemoryInfo GetInfo()
+ {
+ ulong size = PagesCount * KPageTableBase.PageSize;
+
+ return new KMemoryInfo(
+ BaseAddress,
+ size,
+ State,
+ Permission,
+ Attribute,
+ SourcePermission,
+ IpcRefCount,
+ DeviceRefCount);
+ }
+
+ public int CompareTo(KMemoryBlock other)
+ {
+ if (BaseAddress < other.BaseAddress)
+ {
+ return -1;
+ }
+ else if (BaseAddress <= other.BaseAddress + other.PagesCount * KPageTableBase.PageSize - 1UL)
+ {
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ public int CompareTo(ulong address)
+ {
+ if (address < BaseAddress)
+ {
+ return 1;
+ }
+ else if (address <= BaseAddress + PagesCount * KPageTableBase.PageSize - 1UL)
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs
new file mode 100644
index 00000000..e9146aeb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs
@@ -0,0 +1,288 @@
+using Ryujinx.Common.Collections;
+using Ryujinx.Horizon.Common;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryBlockManager
+ {
+ private const int PageSize = KPageTableBase.PageSize;
+
+ private readonly IntrusiveRedBlackTree<KMemoryBlock> _blockTree;
+
+ public int BlocksCount => _blockTree.Count;
+
+ private KMemoryBlockSlabManager _slabManager;
+
+ private ulong _addrSpaceStart;
+ private ulong _addrSpaceEnd;
+
+ public KMemoryBlockManager()
+ {
+ _blockTree = new IntrusiveRedBlackTree<KMemoryBlock>();
+ }
+
+ public Result Initialize(ulong addrSpaceStart, ulong addrSpaceEnd, KMemoryBlockSlabManager slabManager)
+ {
+ _slabManager = slabManager;
+ _addrSpaceStart = addrSpaceStart;
+ _addrSpaceEnd = addrSpaceEnd;
+
+ // First insertion will always need only a single block, because there's nothing to split.
+ if (!slabManager.CanAllocate(1))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize;
+
+ _blockTree.Add(new KMemoryBlock(
+ addrSpaceStart,
+ addrSpacePagesCount,
+ MemoryState.Unmapped,
+ KMemoryPermission.None,
+ MemoryAttribute.None));
+
+ return Result.Success;
+ }
+
+ public void InsertBlock(
+ ulong baseAddress,
+ ulong pagesCount,
+ MemoryState oldState,
+ KMemoryPermission oldPermission,
+ MemoryAttribute oldAttribute,
+ MemoryState newState,
+ KMemoryPermission newPermission,
+ MemoryAttribute newAttribute)
+ {
+ // Insert new block on the list only on areas where the state
+ // of the block matches the state specified on the old* state
+ // arguments, otherwise leave it as is.
+
+ int oldCount = _blockTree.Count;
+
+ oldAttribute |= MemoryAttribute.IpcAndDeviceMapped;
+
+ ulong endAddr = baseAddress + pagesCount * PageSize;
+
+ KMemoryBlock currBlock = FindBlock(baseAddress);
+
+ while (currBlock != null)
+ {
+ ulong currBaseAddr = currBlock.BaseAddress;
+ ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr;
+
+ if (baseAddress < currEndAddr && currBaseAddr < endAddr)
+ {
+ MemoryAttribute currBlockAttr = currBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped;
+
+ if (currBlock.State != oldState ||
+ currBlock.Permission != oldPermission ||
+ currBlockAttr != oldAttribute)
+ {
+ currBlock = currBlock.Successor;
+
+ continue;
+ }
+
+ if (baseAddress > currBaseAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
+ _blockTree.Add(newBlock);
+ }
+
+ if (endAddr < currEndAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
+ _blockTree.Add(newBlock);
+ currBlock = newBlock;
+ }
+
+ currBlock.SetState(newPermission, newState, newAttribute);
+
+ currBlock = MergeEqualStateNeighbors(currBlock);
+ }
+
+ if (currEndAddr - 1 >= endAddr - 1)
+ {
+ break;
+ }
+
+ currBlock = currBlock.Successor;
+ }
+
+ _slabManager.Count += _blockTree.Count - oldCount;
+
+ ValidateInternalState();
+ }
+
+ public void InsertBlock(
+ ulong baseAddress,
+ ulong pagesCount,
+ MemoryState state,
+ KMemoryPermission permission = KMemoryPermission.None,
+ MemoryAttribute attribute = MemoryAttribute.None)
+ {
+ // Inserts new block at the list, replacing and splitting
+ // existing blocks as needed.
+
+ int oldCount = _blockTree.Count;
+
+ ulong endAddr = baseAddress + pagesCount * PageSize;
+
+ KMemoryBlock currBlock = FindBlock(baseAddress);
+
+ while (currBlock != null)
+ {
+ ulong currBaseAddr = currBlock.BaseAddress;
+ ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr;
+
+ if (baseAddress < currEndAddr && currBaseAddr < endAddr)
+ {
+ if (baseAddress > currBaseAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
+ _blockTree.Add(newBlock);
+ }
+
+ if (endAddr < currEndAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
+ _blockTree.Add(newBlock);
+ currBlock = newBlock;
+ }
+
+ currBlock.SetState(permission, state, attribute);
+
+ currBlock = MergeEqualStateNeighbors(currBlock);
+ }
+
+ if (currEndAddr - 1 >= endAddr - 1)
+ {
+ break;
+ }
+
+ currBlock = currBlock.Successor;
+ }
+
+ _slabManager.Count += _blockTree.Count - oldCount;
+
+ ValidateInternalState();
+ }
+
+ public delegate void BlockMutator(KMemoryBlock block, KMemoryPermission newPerm);
+
+ public void InsertBlock(
+ ulong baseAddress,
+ ulong pagesCount,
+ BlockMutator blockMutate,
+ KMemoryPermission permission = KMemoryPermission.None)
+ {
+ // Inserts new block at the list, replacing and splitting
+ // existing blocks as needed, then calling the callback
+ // function on the new block.
+
+ int oldCount = _blockTree.Count;
+
+ ulong endAddr = baseAddress + pagesCount * PageSize;
+
+ KMemoryBlock currBlock = FindBlock(baseAddress);
+
+ while (currBlock != null)
+ {
+ ulong currBaseAddr = currBlock.BaseAddress;
+ ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr;
+
+ if (baseAddress < currEndAddr && currBaseAddr < endAddr)
+ {
+ if (baseAddress > currBaseAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress);
+ _blockTree.Add(newBlock);
+ }
+
+ if (endAddr < currEndAddr)
+ {
+ KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr);
+ _blockTree.Add(newBlock);
+ currBlock = newBlock;
+ }
+
+ blockMutate(currBlock, permission);
+
+ currBlock = MergeEqualStateNeighbors(currBlock);
+ }
+
+ if (currEndAddr - 1 >= endAddr - 1)
+ {
+ break;
+ }
+
+ currBlock = currBlock.Successor;
+ }
+
+ _slabManager.Count += _blockTree.Count - oldCount;
+
+ ValidateInternalState();
+ }
+
+ [Conditional("DEBUG")]
+ private void ValidateInternalState()
+ {
+ ulong expectedAddress = 0;
+
+ KMemoryBlock currBlock = FindBlock(_addrSpaceStart);
+
+ while (currBlock != null)
+ {
+ Debug.Assert(currBlock.BaseAddress == expectedAddress);
+
+ expectedAddress = currBlock.BaseAddress + currBlock.PagesCount * PageSize;
+
+ currBlock = currBlock.Successor;
+ }
+
+ Debug.Assert(expectedAddress == _addrSpaceEnd);
+ }
+
+ private KMemoryBlock MergeEqualStateNeighbors(KMemoryBlock block)
+ {
+ KMemoryBlock previousBlock = block.Predecessor;
+ KMemoryBlock nextBlock = block.Successor;
+
+ if (previousBlock != null && BlockStateEquals(block, previousBlock))
+ {
+ _blockTree.Remove(block);
+
+ previousBlock.AddPages(block.PagesCount);
+
+ block = previousBlock;
+ }
+
+ if (nextBlock != null && BlockStateEquals(block, nextBlock))
+ {
+ _blockTree.Remove(nextBlock);
+
+ block.AddPages(nextBlock.PagesCount);
+ }
+
+ return block;
+ }
+
+ private static bool BlockStateEquals(KMemoryBlock lhs, KMemoryBlock rhs)
+ {
+ return lhs.State == rhs.State &&
+ lhs.Permission == rhs.Permission &&
+ lhs.Attribute == rhs.Attribute &&
+ lhs.SourcePermission == rhs.SourcePermission &&
+ lhs.DeviceRefCount == rhs.DeviceRefCount &&
+ lhs.IpcRefCount == rhs.IpcRefCount;
+ }
+
+ public KMemoryBlock FindBlock(ulong address)
+ {
+ return _blockTree.GetNodeByKey(address);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs
new file mode 100644
index 00000000..8732b507
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryBlockSlabManager
+ {
+ private ulong _capacityElements;
+
+ public int Count { get; set; }
+
+ public KMemoryBlockSlabManager(ulong capacityElements)
+ {
+ _capacityElements = capacityElements;
+ }
+
+ public bool CanAllocate(int count)
+ {
+ return (ulong)(Count + count) <= _capacityElements;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
new file mode 100644
index 00000000..af070ac2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
@@ -0,0 +1,36 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryInfo
+ {
+ public ulong Address { get; }
+ public ulong Size { get; }
+
+ public MemoryState State { get; }
+ public KMemoryPermission Permission { get; }
+ public MemoryAttribute Attribute { get; }
+ public KMemoryPermission SourcePermission { get; }
+
+ public int IpcRefCount { get; }
+ public int DeviceRefCount { get; }
+
+ public KMemoryInfo(
+ ulong address,
+ ulong size,
+ MemoryState state,
+ KMemoryPermission permission,
+ MemoryAttribute attribute,
+ KMemoryPermission sourcePermission,
+ int ipcRefCount,
+ int deviceRefCount)
+ {
+ Address = address;
+ Size = size;
+ State = state;
+ Permission = permission;
+ Attribute = attribute;
+ SourcePermission = sourcePermission;
+ IpcRefCount = ipcRefCount;
+ DeviceRefCount = deviceRefCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs
new file mode 100644
index 00000000..6d0a1658
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs
@@ -0,0 +1,65 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryManager
+ {
+ public KMemoryRegionManager[] MemoryRegions { get; }
+
+ public KMemoryManager(MemorySize size, MemoryArrange arrange)
+ {
+ MemoryRegions = KernelInit.GetMemoryRegions(size, arrange);
+ }
+
+ private KMemoryRegionManager GetMemoryRegion(ulong address)
+ {
+ for (int i = 0; i < MemoryRegions.Length; i++)
+ {
+ var region = MemoryRegions[i];
+
+ if (address >= region.Address && address < region.EndAddr)
+ {
+ return region;
+ }
+ }
+
+ return null;
+ }
+
+ public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
+ {
+ IncrementOrDecrementPagesReferenceCount(address, pagesCount, true);
+ }
+
+ public void DecrementPagesReferenceCount(ulong address, ulong pagesCount)
+ {
+ IncrementOrDecrementPagesReferenceCount(address, pagesCount, false);
+ }
+
+ private void IncrementOrDecrementPagesReferenceCount(ulong address, ulong pagesCount, bool increment)
+ {
+ while (pagesCount != 0)
+ {
+ var region = GetMemoryRegion(address);
+
+ ulong countToProcess = Math.Min(pagesCount, region.GetPageOffsetFromEnd(address));
+
+ lock (region)
+ {
+ if (increment)
+ {
+ region.IncrementPagesReferenceCount(address, countToProcess);
+ }
+ else
+ {
+ region.DecrementPagesReferenceCount(address, countToProcess);
+ }
+ }
+
+ pagesCount -= countToProcess;
+ address += countToProcess * KPageTableBase.PageSize;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs
new file mode 100644
index 00000000..4596b15d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs
@@ -0,0 +1,242 @@
+using Ryujinx.Horizon.Common;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KMemoryRegionManager
+ {
+ private readonly KPageHeap _pageHeap;
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+ public ulong EndAddr => Address + Size;
+
+ private readonly ushort[] _pageReferenceCounts;
+
+ public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
+ {
+ Address = address;
+ Size = size;
+
+ _pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
+
+ _pageHeap = new KPageHeap(address, size);
+ _pageHeap.Free(address, size / KPageTableBase.PageSize);
+ _pageHeap.UpdateUsedSize();
+ }
+
+ public Result AllocatePages(out KPageList pageList, ulong pagesCount)
+ {
+ if (pagesCount == 0)
+ {
+ pageList = new KPageList();
+
+ return Result.Success;
+ }
+
+ lock (_pageHeap)
+ {
+ Result result = AllocatePagesImpl(out pageList, pagesCount, false);
+
+ if (result == Result.Success)
+ {
+ foreach (var node in pageList)
+ {
+ IncrementPagesReferenceCount(node.Address, node.PagesCount);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards)
+ {
+ if (pagesCount == 0)
+ {
+ return 0;
+ }
+
+ lock (_pageHeap)
+ {
+ ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards);
+
+ if (address != 0)
+ {
+ IncrementPagesReferenceCount(address, pagesCount);
+ context.CommitMemory(address - DramMemoryMap.DramBase, pagesCount * KPageTableBase.PageSize);
+ }
+
+ return address;
+ }
+ }
+
+ private Result AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random)
+ {
+ pageList = new KPageList();
+
+ int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
+
+ if (heapIndex < 0)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ for (int index = heapIndex; index >= 0; index--)
+ {
+ ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index);
+
+ while (pagesCount >= pagesPerAlloc)
+ {
+ ulong allocatedBlock = _pageHeap.AllocateBlock(index, random);
+
+ if (allocatedBlock == 0)
+ {
+ break;
+ }
+
+ Result result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
+
+ if (result != Result.Success)
+ {
+ FreePages(pageList);
+ _pageHeap.Free(allocatedBlock, pagesPerAlloc);
+
+ return result;
+ }
+
+ pagesCount -= pagesPerAlloc;
+ }
+ }
+
+ if (pagesCount != 0)
+ {
+ FreePages(pageList);
+
+ return KernelResult.OutOfMemory;
+ }
+
+ return Result.Success;
+ }
+
+ private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random)
+ {
+ int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages);
+
+ ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random);
+
+ if (allocatedBlock == 0)
+ {
+ return 0;
+ }
+
+ ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex);
+
+ if (allocatedPages > pagesCount)
+ {
+ _pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
+ }
+
+ return allocatedBlock;
+ }
+
+ public void FreePage(ulong address)
+ {
+ lock (_pageHeap)
+ {
+ _pageHeap.Free(address, 1);
+ }
+ }
+
+ public void FreePages(KPageList pageList)
+ {
+ lock (_pageHeap)
+ {
+ foreach (KPageNode pageNode in pageList)
+ {
+ _pageHeap.Free(pageNode.Address, pageNode.PagesCount);
+ }
+ }
+ }
+
+ public void FreePages(ulong address, ulong pagesCount)
+ {
+ lock (_pageHeap)
+ {
+ _pageHeap.Free(address, pagesCount);
+ }
+ }
+
+ public ulong GetFreePages()
+ {
+ lock (_pageHeap)
+ {
+ return _pageHeap.GetFreePagesCount();
+ }
+ }
+
+ public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
+ {
+ ulong index = GetPageOffset(address);
+ ulong endIndex = index + pagesCount;
+
+ while (index < endIndex)
+ {
+ ushort referenceCount = ++_pageReferenceCounts[index];
+ Debug.Assert(referenceCount >= 1);
+
+ index++;
+ }
+ }
+
+ public void DecrementPagesReferenceCount(ulong address, ulong pagesCount)
+ {
+ ulong index = GetPageOffset(address);
+ ulong endIndex = index + pagesCount;
+
+ ulong freeBaseIndex = 0;
+ ulong freePagesCount = 0;
+
+ while (index < endIndex)
+ {
+ Debug.Assert(_pageReferenceCounts[index] > 0);
+ ushort referenceCount = --_pageReferenceCounts[index];
+
+ if (referenceCount == 0)
+ {
+ if (freePagesCount != 0)
+ {
+ freePagesCount++;
+ }
+ else
+ {
+ freeBaseIndex = index;
+ freePagesCount = 1;
+ }
+ }
+ else if (freePagesCount != 0)
+ {
+ FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount);
+ freePagesCount = 0;
+ }
+
+ index++;
+ }
+
+ if (freePagesCount != 0)
+ {
+ FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount);
+ }
+ }
+
+ public ulong GetPageOffset(ulong address)
+ {
+ return (address - Address) / KPageTableBase.PageSize;
+ }
+
+ public ulong GetPageOffsetFromEnd(ulong address)
+ {
+ return (EndAddr - address) / KPageTableBase.PageSize;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
new file mode 100644
index 00000000..fa090b02
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
@@ -0,0 +1,298 @@
+using Ryujinx.Common;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KPageBitmap
+ {
+ private struct RandomNumberGenerator
+ {
+ private uint _entropy;
+ private uint _bitsAvailable;
+
+ private void RefreshEntropy()
+ {
+ _entropy = 0;
+ _bitsAvailable = sizeof(uint) * 8;
+ }
+
+ private bool GenerateRandomBit()
+ {
+ if (_bitsAvailable == 0)
+ {
+ RefreshEntropy();
+ }
+
+ bool bit = (_entropy & 1) != 0;
+
+ _entropy >>= 1;
+ _bitsAvailable--;
+
+ return bit;
+ }
+
+ public int SelectRandomBit(ulong bitmap)
+ {
+ int selected = 0;
+
+ int bitsCount = UInt64BitSize / 2;
+ ulong mask = (1UL << bitsCount) - 1;
+
+ while (bitsCount != 0)
+ {
+ ulong low = bitmap & mask;
+ ulong high = (bitmap >> bitsCount) & mask;
+
+ bool chooseLow;
+
+ if (high == 0)
+ {
+ chooseLow = true;
+ }
+ else if (low == 0)
+ {
+ chooseLow = false;
+ }
+ else
+ {
+ chooseLow = GenerateRandomBit();
+ }
+
+ if (chooseLow)
+ {
+ bitmap = low;
+ }
+ else
+ {
+ bitmap = high;
+ selected += bitsCount;
+ }
+
+ bitsCount /= 2;
+ mask >>= bitsCount;
+ }
+
+ return selected;
+ }
+ }
+
+ private const int UInt64BitSize = sizeof(ulong) * 8;
+ private const int MaxDepth = 4;
+
+ private readonly RandomNumberGenerator _rng;
+ private readonly ArraySegment<ulong>[] _bitStorages;
+ private int _usedDepths;
+
+ public int BitsCount { get; private set; }
+
+ public int HighestDepthIndex => _usedDepths - 1;
+
+ public KPageBitmap()
+ {
+ _rng = new RandomNumberGenerator();
+ _bitStorages = new ArraySegment<ulong>[MaxDepth];
+ }
+
+ public ArraySegment<ulong> Initialize(ArraySegment<ulong> storage, ulong size)
+ {
+ _usedDepths = GetRequiredDepth(size);
+
+ for (int depth = HighestDepthIndex; depth >= 0; depth--)
+ {
+ _bitStorages[depth] = storage;
+ size = BitUtils.DivRoundUp<ulong>(size, (ulong)UInt64BitSize);
+ storage = storage.Slice((int)size);
+ }
+
+ return storage;
+ }
+
+ public ulong FindFreeBlock(bool random)
+ {
+ ulong offset = 0;
+ int depth = 0;
+
+ if (random)
+ {
+ do
+ {
+ ulong v = _bitStorages[depth][(int)offset];
+
+ if (v == 0)
+ {
+ return ulong.MaxValue;
+ }
+
+ offset = offset * UInt64BitSize + (ulong)_rng.SelectRandomBit(v);
+ }
+ while (++depth < _usedDepths);
+ }
+ else
+ {
+ do
+ {
+ ulong v = _bitStorages[depth][(int)offset];
+
+ if (v == 0)
+ {
+ return ulong.MaxValue;
+ }
+
+ offset = offset * UInt64BitSize + (ulong)BitOperations.TrailingZeroCount(v);
+ }
+ while (++depth < _usedDepths);
+ }
+
+ return offset;
+ }
+
+ public void SetBit(ulong offset)
+ {
+ SetBit(HighestDepthIndex, offset);
+ BitsCount++;
+ }
+
+ public void ClearBit(ulong offset)
+ {
+ ClearBit(HighestDepthIndex, offset);
+ BitsCount--;
+ }
+
+ public bool ClearRange(ulong offset, int count)
+ {
+ int depth = HighestDepthIndex;
+ var bits = _bitStorages[depth];
+
+ int bitInd = (int)(offset / UInt64BitSize);
+
+ if (count < UInt64BitSize)
+ {
+ int shift = (int)(offset % UInt64BitSize);
+
+ ulong mask = ((1UL << count) - 1) << shift;
+
+ ulong v = bits[bitInd];
+
+ if ((v & mask) != mask)
+ {
+ return false;
+ }
+
+ v &= ~mask;
+ bits[bitInd] = v;
+
+ if (v == 0)
+ {
+ ClearBit(depth - 1, (ulong)bitInd);
+ }
+ }
+ else
+ {
+ int remaining = count;
+ int i = 0;
+
+ do
+ {
+ if (bits[bitInd + i++] != ulong.MaxValue)
+ {
+ return false;
+ }
+
+ remaining -= UInt64BitSize;
+ }
+ while (remaining > 0);
+
+ remaining = count;
+ i = 0;
+
+ do
+ {
+ bits[bitInd + i] = 0;
+ ClearBit(depth - 1, (ulong)(bitInd + i));
+ i++;
+ remaining -= UInt64BitSize;
+ }
+ while (remaining > 0);
+ }
+
+ BitsCount -= count;
+ return true;
+ }
+
+ private void SetBit(int depth, ulong offset)
+ {
+ while (depth >= 0)
+ {
+ int ind = (int)(offset / UInt64BitSize);
+ int which = (int)(offset % UInt64BitSize);
+
+ ulong mask = 1UL << which;
+
+ ulong v = _bitStorages[depth][ind];
+
+ _bitStorages[depth][ind] = v | mask;
+
+ if (v != 0)
+ {
+ break;
+ }
+
+ offset = (ulong)ind;
+ depth--;
+ }
+ }
+
+ private void ClearBit(int depth, ulong offset)
+ {
+ while (depth >= 0)
+ {
+ int ind = (int)(offset / UInt64BitSize);
+ int which = (int)(offset % UInt64BitSize);
+
+ ulong mask = 1UL << which;
+
+ ulong v = _bitStorages[depth][ind];
+
+ v &= ~mask;
+
+ _bitStorages[depth][ind] = v;
+
+ if (v != 0)
+ {
+ break;
+ }
+
+ offset = (ulong)ind;
+ depth--;
+ }
+ }
+
+ private static int GetRequiredDepth(ulong regionSize)
+ {
+ int depth = 0;
+
+ do
+ {
+ regionSize /= UInt64BitSize;
+ depth++;
+ }
+ while (regionSize != 0);
+
+ return depth;
+ }
+
+ public static int CalculateManagementOverheadSize(ulong regionSize)
+ {
+ int overheadBits = 0;
+
+ for (int depth = GetRequiredDepth(regionSize) - 1; depth >= 0; depth--)
+ {
+ regionSize = BitUtils.DivRoundUp<ulong>(regionSize, UInt64BitSize);
+ overheadBits += (int)regionSize;
+ }
+
+ return overheadBits * sizeof(ulong);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
new file mode 100644
index 00000000..c3586ed7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
@@ -0,0 +1,283 @@
+using Ryujinx.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KPageHeap
+ {
+ private class Block
+ {
+ private KPageBitmap _bitmap = new KPageBitmap();
+ private ulong _heapAddress;
+ private ulong _endOffset;
+
+ public int Shift { get; private set; }
+ public int NextShift { get; private set; }
+ public ulong Size => 1UL << Shift;
+ public int PagesCount => (int)(Size / KPageTableBase.PageSize);
+ public int FreeBlocksCount => _bitmap.BitsCount;
+ public int FreePagesCount => FreeBlocksCount * PagesCount;
+
+ public ArraySegment<ulong> Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment<ulong> bitStorage)
+ {
+ Shift = blockShift;
+ NextShift = nextBlockShift;
+
+ ulong endAddress = address + size;
+
+ ulong align = nextBlockShift != 0
+ ? 1UL << nextBlockShift
+ : 1UL << blockShift;
+
+ address = BitUtils.AlignDown(address, align);
+ endAddress = BitUtils.AlignUp (endAddress, align);
+
+ _heapAddress = address;
+ _endOffset = (endAddress - address) / (1UL << blockShift);
+
+ return _bitmap.Initialize(bitStorage, _endOffset);
+ }
+
+ public ulong PushBlock(ulong address)
+ {
+ ulong offset = (address - _heapAddress) >> Shift;
+
+ _bitmap.SetBit(offset);
+
+ if (NextShift != 0)
+ {
+ int diff = 1 << (NextShift - Shift);
+
+ offset = BitUtils.AlignDown(offset, (ulong)diff);
+
+ if (_bitmap.ClearRange(offset, diff))
+ {
+ return _heapAddress + (offset << Shift);
+ }
+ }
+
+ return 0;
+ }
+
+ public ulong PopBlock(bool random)
+ {
+ long sOffset = (long)_bitmap.FindFreeBlock(random);
+
+ if (sOffset < 0L)
+ {
+ return 0;
+ }
+
+ ulong offset = (ulong)sOffset;
+
+ _bitmap.ClearBit(offset);
+
+ return _heapAddress + (offset << Shift);
+ }
+
+ public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift)
+ {
+ ulong currBlockSize = 1UL << currBlockShift;
+ ulong nextBlockSize = 1UL << nextBlockShift;
+ ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize;
+ return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize);
+ }
+ }
+
+ private static readonly int[] _memoryBlockPageShifts = new int[] { 12, 16, 21, 22, 25, 29, 30 };
+
+ private readonly ulong _heapAddress;
+ private readonly ulong _heapSize;
+ private ulong _usedSize;
+ private readonly int _blocksCount;
+ private readonly Block[] _blocks;
+
+ public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts)
+ {
+ }
+
+ public KPageHeap(ulong address, ulong size, int[] blockShifts)
+ {
+ _heapAddress = address;
+ _heapSize = size;
+ _blocksCount = blockShifts.Length;
+ _blocks = new Block[_memoryBlockPageShifts.Length];
+
+ var currBitmapStorage = new ArraySegment<ulong>(new ulong[CalculateManagementOverheadSize(size, blockShifts)]);
+
+ for (int i = 0; i < blockShifts.Length; i++)
+ {
+ int currBlockShift = blockShifts[i];
+ int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
+
+ _blocks[i] = new Block();
+
+ currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage);
+ }
+ }
+
+ public void UpdateUsedSize()
+ {
+ _usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize);
+ }
+
+ public ulong GetFreePagesCount()
+ {
+ ulong freeCount = 0;
+
+ for (int i = 0; i < _blocksCount; i++)
+ {
+ freeCount += (ulong)_blocks[i].FreePagesCount;
+ }
+
+ return freeCount;
+ }
+
+ public ulong AllocateBlock(int index, bool random)
+ {
+ ulong neededSize = _blocks[index].Size;
+
+ for (int i = index; i < _blocksCount; i++)
+ {
+ ulong address = _blocks[i].PopBlock(random);
+
+ if (address != 0)
+ {
+ ulong allocatedSize = _blocks[i].Size;
+
+ if (allocatedSize > neededSize)
+ {
+ Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize);
+ }
+
+ return address;
+ }
+ }
+
+ return 0;
+ }
+
+ private void FreeBlock(ulong block, int index)
+ {
+ do
+ {
+ block = _blocks[index++].PushBlock(block);
+ }
+ while (block != 0);
+ }
+
+ public void Free(ulong address, ulong pagesCount)
+ {
+ if (pagesCount == 0)
+ {
+ return;
+ }
+
+ int bigIndex = _blocksCount - 1;
+
+ ulong start = address;
+ ulong end = address + pagesCount * KPageTableBase.PageSize;
+ ulong beforeStart = start;
+ ulong beforeEnd = start;
+ ulong afterStart = end;
+ ulong afterEnd = end;
+
+ while (bigIndex >= 0)
+ {
+ ulong blockSize = _blocks[bigIndex].Size;
+
+ ulong bigStart = BitUtils.AlignUp (start, blockSize);
+ ulong bigEnd = BitUtils.AlignDown(end, blockSize);
+
+ if (bigStart < bigEnd)
+ {
+ for (ulong block = bigStart; block < bigEnd; block += blockSize)
+ {
+ FreeBlock(block, bigIndex);
+ }
+
+ beforeEnd = bigStart;
+ afterStart = bigEnd;
+
+ break;
+ }
+
+ bigIndex--;
+ }
+
+ for (int i = bigIndex - 1; i >= 0; i--)
+ {
+ ulong blockSize = _blocks[i].Size;
+
+ while (beforeStart + blockSize <= beforeEnd)
+ {
+ beforeEnd -= blockSize;
+ FreeBlock(beforeEnd, i);
+ }
+ }
+
+ for (int i = bigIndex - 1; i >= 0; i--)
+ {
+ ulong blockSize = _blocks[i].Size;
+
+ while (afterStart + blockSize <= afterEnd)
+ {
+ FreeBlock(afterStart, i);
+ afterStart += blockSize;
+ }
+ }
+ }
+
+ public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages)
+ {
+ ulong targetPages = Math.Max(pagesCount, alignPages);
+
+ for (int i = 0; i < _memoryBlockPageShifts.Length; i++)
+ {
+ if (targetPages <= GetBlockPagesCount(i))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public static int GetBlockIndex(ulong pagesCount)
+ {
+ for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--)
+ {
+ if (pagesCount >= GetBlockPagesCount(i))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public static ulong GetBlockSize(int index)
+ {
+ return 1UL << _memoryBlockPageShifts[index];
+ }
+
+ public static ulong GetBlockPagesCount(int index)
+ {
+ return GetBlockSize(index) / KPageTableBase.PageSize;
+ }
+
+ private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts)
+ {
+ int overheadSize = 0;
+
+ for (int i = 0; i < blockShifts.Length; i++)
+ {
+ int currBlockShift = blockShifts[i];
+ int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
+ overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift);
+ }
+
+ return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs
new file mode 100644
index 00000000..3149faa9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs
@@ -0,0 +1,97 @@
+using Ryujinx.Horizon.Common;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KPageList : IEnumerable<KPageNode>
+ {
+ public LinkedList<KPageNode> Nodes { get; }
+
+ public KPageList()
+ {
+ Nodes = new LinkedList<KPageNode>();
+ }
+
+ public Result AddRange(ulong address, ulong pagesCount)
+ {
+ if (pagesCount != 0)
+ {
+ if (Nodes.Last != null)
+ {
+ KPageNode lastNode = Nodes.Last.Value;
+
+ if (lastNode.Address + lastNode.PagesCount * KPageTableBase.PageSize == address)
+ {
+ address = lastNode.Address;
+ pagesCount += lastNode.PagesCount;
+
+ Nodes.RemoveLast();
+ }
+ }
+
+ Nodes.AddLast(new KPageNode(address, pagesCount));
+ }
+
+ return Result.Success;
+ }
+
+ public ulong GetPagesCount()
+ {
+ ulong sum = 0;
+
+ foreach (KPageNode node in Nodes)
+ {
+ sum += node.PagesCount;
+ }
+
+ return sum;
+ }
+
+ public bool IsEqual(KPageList other)
+ {
+ LinkedListNode<KPageNode> thisNode = Nodes.First;
+ LinkedListNode<KPageNode> otherNode = other.Nodes.First;
+
+ while (thisNode != null && otherNode != null)
+ {
+ if (thisNode.Value.Address != otherNode.Value.Address ||
+ thisNode.Value.PagesCount != otherNode.Value.PagesCount)
+ {
+ return false;
+ }
+
+ thisNode = thisNode.Next;
+ otherNode = otherNode.Next;
+ }
+
+ return thisNode == null && otherNode == null;
+ }
+
+ public void IncrementPagesReferenceCount(KMemoryManager manager)
+ {
+ foreach (var node in this)
+ {
+ manager.IncrementPagesReferenceCount(node.Address, node.PagesCount);
+ }
+ }
+
+ public void DecrementPagesReferenceCount(KMemoryManager manager)
+ {
+ foreach (var node in this)
+ {
+ manager.DecrementPagesReferenceCount(node.Address, node.PagesCount);
+ }
+ }
+
+ public IEnumerator<KPageNode> GetEnumerator()
+ {
+ return Nodes.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs
new file mode 100644
index 00000000..ada41687
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ struct KPageNode
+ {
+ public ulong Address;
+ public ulong PagesCount;
+
+ public KPageNode(ulong address, ulong pagesCount)
+ {
+ Address = address;
+ PagesCount = pagesCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs
new file mode 100644
index 00000000..28e9f90a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs
@@ -0,0 +1,229 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KPageTable : KPageTableBase
+ {
+ private readonly IVirtualMemoryManager _cpuMemory;
+
+ protected override bool Supports4KBPages => _cpuMemory.Supports4KBPages;
+
+ public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
+ {
+ _cpuMemory = cpuMemory;
+ }
+
+ /// <inheritdoc/>
+ protected override IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
+ {
+ return _cpuMemory.GetHostRegions(va, size);
+ }
+
+ /// <inheritdoc/>
+ protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList)
+ {
+ var ranges = _cpuMemory.GetPhysicalRegions(va, size);
+ foreach (var range in ranges)
+ {
+ pageList.AddRange(range.Address + DramMemoryMap.DramBase, range.Size / PageSize);
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override ReadOnlySpan<byte> GetSpan(ulong va, int size)
+ {
+ return _cpuMemory.GetSpan(va, size);
+ }
+
+ /// <inheritdoc/>
+ protected override Result MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission)
+ {
+ KPageList pageList = new KPageList();
+ GetPhysicalRegions(src, pagesCount * PageSize, pageList);
+
+ Result result = Reprotect(src, pagesCount, KMemoryPermission.None);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = MapPages(dst, pageList, newDstPermission, MemoryMapFlags.Private, false, 0);
+
+ if (result != Result.Success)
+ {
+ Result reprotectResult = Reprotect(src, pagesCount, oldSrcPermission);
+ Debug.Assert(reprotectResult == Result.Success);
+ }
+
+ return result;
+ }
+
+ /// <inheritdoc/>
+ protected override Result UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission)
+ {
+ ulong size = pagesCount * PageSize;
+
+ KPageList srcPageList = new KPageList();
+ KPageList dstPageList = new KPageList();
+
+ GetPhysicalRegions(src, size, srcPageList);
+ GetPhysicalRegions(dst, size, dstPageList);
+
+ if (!dstPageList.IsEqual(srcPageList))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ Result result = Unmap(dst, pagesCount);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = Reprotect(src, pagesCount, newSrcPermission);
+
+ if (result != Result.Success)
+ {
+ Result mapResult = MapPages(dst, dstPageList, oldDstPermission, MemoryMapFlags.Private, false, 0);
+ Debug.Assert(mapResult == Result.Success);
+ }
+
+ return result;
+ }
+
+ /// <inheritdoc/>
+ protected override Result MapPages(
+ ulong dstVa,
+ ulong pagesCount,
+ ulong srcPa,
+ KMemoryPermission permission,
+ MemoryMapFlags flags,
+ bool shouldFillPages,
+ byte fillValue)
+ {
+ ulong size = pagesCount * PageSize;
+
+ Context.CommitMemory(srcPa - DramMemoryMap.DramBase, size);
+
+ _cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size, flags);
+
+ if (DramMemoryMap.IsHeapPhysicalAddress(srcPa))
+ {
+ Context.MemoryManager.IncrementPagesReferenceCount(srcPa, pagesCount);
+ }
+
+ if (shouldFillPages)
+ {
+ _cpuMemory.Fill(dstVa, size, fillValue);
+ }
+
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override Result MapPages(
+ ulong address,
+ KPageList pageList,
+ KMemoryPermission permission,
+ MemoryMapFlags flags,
+ bool shouldFillPages,
+ byte fillValue)
+ {
+ using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList);
+
+ ulong currentVa = address;
+
+ foreach (var pageNode in pageList)
+ {
+ ulong addr = pageNode.Address - DramMemoryMap.DramBase;
+ ulong size = pageNode.PagesCount * PageSize;
+
+ Context.CommitMemory(addr, size);
+
+ _cpuMemory.Map(currentVa, addr, size, flags);
+
+ if (shouldFillPages)
+ {
+ _cpuMemory.Fill(currentVa, size, fillValue);
+ }
+
+ currentVa += size;
+ }
+
+ scopedPageList.SignalSuccess();
+
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override Result MapForeign(IEnumerable<HostMemoryRange> regions, ulong va, ulong size)
+ {
+ ulong offset = 0;
+
+ foreach (var region in regions)
+ {
+ _cpuMemory.MapForeign(va + offset, region.Address, region.Size);
+
+ offset += region.Size;
+ }
+
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override Result Unmap(ulong address, ulong pagesCount)
+ {
+ KPageList pagesToClose = new KPageList();
+
+ var regions = _cpuMemory.GetPhysicalRegions(address, pagesCount * PageSize);
+
+ foreach (var region in regions)
+ {
+ ulong pa = region.Address + DramMemoryMap.DramBase;
+ if (DramMemoryMap.IsHeapPhysicalAddress(pa))
+ {
+ pagesToClose.AddRange(pa, region.Size / PageSize);
+ }
+ }
+
+ _cpuMemory.Unmap(address, pagesCount * PageSize);
+
+ pagesToClose.DecrementPagesReferenceCount(Context.MemoryManager);
+
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
+ {
+ // TODO.
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
+ {
+ // TODO.
+ return Result.Success;
+ }
+
+ /// <inheritdoc/>
+ protected override void SignalMemoryTracking(ulong va, ulong size, bool write)
+ {
+ _cpuMemory.SignalMemoryTracking(va, size, write);
+ }
+
+ /// <inheritdoc/>
+ protected override void Write(ulong va, ReadOnlySpan<byte> data)
+ {
+ _cpuMemory.Write(va, data);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
new file mode 100644
index 00000000..614eb527
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
@@ -0,0 +1,3043 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using Ryujinx.Memory.Range;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ abstract class KPageTableBase
+ {
+ private static readonly int[] MappingUnitSizes = new int[]
+ {
+ 0x1000,
+ 0x10000,
+ 0x200000,
+ 0x400000,
+ 0x2000000,
+ 0x40000000
+ };
+
+ public const int PageSize = 0x1000;
+
+ private const int KMemoryBlockSize = 0x40;
+
+ // We need 2 blocks for the case where a big block
+ // needs to be split in 2, plus one block that will be the new one inserted.
+ private const int MaxBlocksNeededForInsertion = 2;
+
+ protected readonly KernelContext Context;
+ protected virtual bool Supports4KBPages => true;
+
+ public ulong AddrSpaceStart { get; private set; }
+ public ulong AddrSpaceEnd { get; private set; }
+
+ public ulong CodeRegionStart { get; private set; }
+ public ulong CodeRegionEnd { get; private set; }
+
+ public ulong HeapRegionStart { get; private set; }
+ public ulong HeapRegionEnd { get; private set; }
+
+ private ulong _currentHeapAddr;
+
+ public ulong AliasRegionStart { get; private set; }
+ public ulong AliasRegionEnd { get; private set; }
+
+ public ulong StackRegionStart { get; private set; }
+ public ulong StackRegionEnd { get; private set; }
+
+ public ulong TlsIoRegionStart { get; private set; }
+ public ulong TlsIoRegionEnd { get; private set; }
+
+ private ulong _heapCapacity;
+
+ public ulong PhysicalMemoryUsage { get; private set; }
+
+ private readonly KMemoryBlockManager _blockManager;
+
+ private MemoryRegion _memRegion;
+
+ private bool _aslrDisabled;
+
+ public int AddrSpaceWidth { get; private set; }
+
+ private bool _isKernel;
+
+ private bool _aslrEnabled;
+
+ private KMemoryBlockSlabManager _slabManager;
+
+ private int _contextId;
+
+ private MersenneTwister _randomNumberGenerator;
+
+ private MemoryFillValue _heapFillValue;
+ private MemoryFillValue _ipcFillValue;
+
+ public KPageTableBase(KernelContext context)
+ {
+ Context = context;
+
+ _blockManager = new KMemoryBlockManager();
+
+ _isKernel = false;
+
+ _heapFillValue = MemoryFillValue.Zero;
+ _ipcFillValue = MemoryFillValue.Zero;
+ }
+
+ private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 };
+
+ public Result InitializeForProcess(
+ AddressSpaceType addrSpaceType,
+ bool aslrEnabled,
+ bool aslrDisabled,
+ MemoryRegion memRegion,
+ ulong address,
+ ulong size,
+ KMemoryBlockSlabManager slabManager)
+ {
+ if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits)
+ {
+ throw new ArgumentException(nameof(addrSpaceType));
+ }
+
+ _contextId = Context.ContextIdManager.GetId();
+
+ ulong addrSpaceBase = 0;
+ ulong addrSpaceSize = 1UL << AddrSpaceSizes[(int)addrSpaceType];
+
+ Result result = CreateUserAddressSpace(
+ addrSpaceType,
+ aslrEnabled,
+ aslrDisabled,
+ addrSpaceBase,
+ addrSpaceSize,
+ memRegion,
+ address,
+ size,
+ slabManager);
+
+ if (result != Result.Success)
+ {
+ Context.ContextIdManager.PutId(_contextId);
+ }
+
+ return result;
+ }
+
+ private class Region
+ {
+ public ulong Start;
+ public ulong End;
+ public ulong Size;
+ public ulong AslrOffset;
+ }
+
+ private Result CreateUserAddressSpace(
+ AddressSpaceType addrSpaceType,
+ bool aslrEnabled,
+ bool aslrDisabled,
+ ulong addrSpaceStart,
+ ulong addrSpaceEnd,
+ MemoryRegion memRegion,
+ ulong address,
+ ulong size,
+ KMemoryBlockSlabManager slabManager)
+ {
+ ulong endAddr = address + size;
+
+ Region aliasRegion = new Region();
+ Region heapRegion = new Region();
+ Region stackRegion = new Region();
+ Region tlsIoRegion = new Region();
+
+ ulong codeRegionSize;
+ ulong stackAndTlsIoStart;
+ ulong stackAndTlsIoEnd;
+ ulong baseAddress;
+
+ switch (addrSpaceType)
+ {
+ case AddressSpaceType.Addr32Bits:
+ aliasRegion.Size = 0x40000000;
+ heapRegion.Size = 0x40000000;
+ stackRegion.Size = 0;
+ tlsIoRegion.Size = 0;
+ CodeRegionStart = 0x200000;
+ codeRegionSize = 0x3fe00000;
+ stackAndTlsIoStart = 0x200000;
+ stackAndTlsIoEnd = 0x40000000;
+ baseAddress = 0x200000;
+ AddrSpaceWidth = 32;
+ break;
+
+ case AddressSpaceType.Addr36Bits:
+ aliasRegion.Size = 0x180000000;
+ heapRegion.Size = 0x180000000;
+ stackRegion.Size = 0;
+ tlsIoRegion.Size = 0;
+ CodeRegionStart = 0x8000000;
+ codeRegionSize = 0x78000000;
+ stackAndTlsIoStart = 0x8000000;
+ stackAndTlsIoEnd = 0x80000000;
+ baseAddress = 0x8000000;
+ AddrSpaceWidth = 36;
+ break;
+
+ case AddressSpaceType.Addr32BitsNoMap:
+ aliasRegion.Size = 0;
+ heapRegion.Size = 0x80000000;
+ stackRegion.Size = 0;
+ tlsIoRegion.Size = 0;
+ CodeRegionStart = 0x200000;
+ codeRegionSize = 0x3fe00000;
+ stackAndTlsIoStart = 0x200000;
+ stackAndTlsIoEnd = 0x40000000;
+ baseAddress = 0x200000;
+ AddrSpaceWidth = 32;
+ break;
+
+ case AddressSpaceType.Addr39Bits:
+ aliasRegion.Size = 0x1000000000;
+ heapRegion.Size = 0x180000000;
+ stackRegion.Size = 0x80000000;
+ tlsIoRegion.Size = 0x1000000000;
+ CodeRegionStart = BitUtils.AlignDown<ulong>(address, 0x200000);
+ codeRegionSize = BitUtils.AlignUp<ulong>(endAddr, 0x200000) - CodeRegionStart;
+ stackAndTlsIoStart = 0;
+ stackAndTlsIoEnd = 0;
+ baseAddress = 0x8000000;
+ AddrSpaceWidth = 39;
+ break;
+
+ default: throw new ArgumentException(nameof(addrSpaceType));
+ }
+
+ CodeRegionEnd = CodeRegionStart + codeRegionSize;
+
+ ulong mapBaseAddress;
+ ulong mapAvailableSize;
+
+ if (CodeRegionStart - baseAddress >= addrSpaceEnd - CodeRegionEnd)
+ {
+ // Has more space before the start of the code region.
+ mapBaseAddress = baseAddress;
+ mapAvailableSize = CodeRegionStart - baseAddress;
+ }
+ else
+ {
+ // Has more space after the end of the code region.
+ mapBaseAddress = CodeRegionEnd;
+ mapAvailableSize = addrSpaceEnd - CodeRegionEnd;
+ }
+
+ ulong mapTotalSize = aliasRegion.Size + heapRegion.Size + stackRegion.Size + tlsIoRegion.Size;
+
+ ulong aslrMaxOffset = mapAvailableSize - mapTotalSize;
+
+ _aslrEnabled = aslrEnabled;
+
+ AddrSpaceStart = addrSpaceStart;
+ AddrSpaceEnd = addrSpaceEnd;
+
+ _slabManager = slabManager;
+
+ if (mapAvailableSize < mapTotalSize)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ if (aslrEnabled)
+ {
+ aliasRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21;
+ heapRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21;
+ stackRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21;
+ tlsIoRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21;
+ }
+
+ // Regions are sorted based on ASLR offset.
+ // When ASLR is disabled, the order is Map, Heap, NewMap and TlsIo.
+ aliasRegion.Start = mapBaseAddress + aliasRegion.AslrOffset;
+ aliasRegion.End = aliasRegion.Start + aliasRegion.Size;
+ heapRegion.Start = mapBaseAddress + heapRegion.AslrOffset;
+ heapRegion.End = heapRegion.Start + heapRegion.Size;
+ stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset;
+ stackRegion.End = stackRegion.Start + stackRegion.Size;
+ tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset;
+ tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size;
+
+ SortRegion(heapRegion, aliasRegion);
+
+ if (stackRegion.Size != 0)
+ {
+ SortRegion(stackRegion, aliasRegion);
+ SortRegion(stackRegion, heapRegion);
+ }
+ else
+ {
+ stackRegion.Start = stackAndTlsIoStart;
+ stackRegion.End = stackAndTlsIoEnd;
+ }
+
+ if (tlsIoRegion.Size != 0)
+ {
+ SortRegion(tlsIoRegion, aliasRegion);
+ SortRegion(tlsIoRegion, heapRegion);
+ SortRegion(tlsIoRegion, stackRegion);
+ }
+ else
+ {
+ tlsIoRegion.Start = stackAndTlsIoStart;
+ tlsIoRegion.End = stackAndTlsIoEnd;
+ }
+
+ AliasRegionStart = aliasRegion.Start;
+ AliasRegionEnd = aliasRegion.End;
+ HeapRegionStart = heapRegion.Start;
+ HeapRegionEnd = heapRegion.End;
+ StackRegionStart = stackRegion.Start;
+ StackRegionEnd = stackRegion.End;
+ TlsIoRegionStart = tlsIoRegion.Start;
+ TlsIoRegionEnd = tlsIoRegion.End;
+
+ // TODO: Check kernel configuration via secure monitor call when implemented to set memory fill values.
+
+ _currentHeapAddr = HeapRegionStart;
+ _heapCapacity = 0;
+ PhysicalMemoryUsage = 0;
+
+ _memRegion = memRegion;
+ _aslrDisabled = aslrDisabled;
+
+ return _blockManager.Initialize(addrSpaceStart, addrSpaceEnd, slabManager);
+ }
+
+ private ulong GetRandomValue(ulong min, ulong max)
+ {
+ return (ulong)GetRandomValue((long)min, (long)max);
+ }
+
+ private long GetRandomValue(long min, long max)
+ {
+ if (_randomNumberGenerator == null)
+ {
+ _randomNumberGenerator = new MersenneTwister(0);
+ }
+
+ return _randomNumberGenerator.GenRandomNumber(min, max);
+ }
+
+ private static void SortRegion(Region lhs, Region rhs)
+ {
+ if (lhs.AslrOffset < rhs.AslrOffset)
+ {
+ rhs.Start += lhs.Size;
+ rhs.End += lhs.Size;
+ }
+ else
+ {
+ lhs.Start += rhs.Size;
+ lhs.End += rhs.Size;
+ }
+ }
+
+ public Result MapPages(ulong address, KPageList pageList, MemoryState state, KMemoryPermission permission)
+ {
+ ulong pagesCount = pageList.GetPagesCount();
+
+ ulong size = pagesCount * PageSize;
+
+ if (!CanContain(address, size, state))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (!IsUnmapped(address, pagesCount * PageSize))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ Result result = MapPages(address, pageList, permission, MemoryMapFlags.None);
+
+ if (result == Result.Success)
+ {
+ _blockManager.InsertBlock(address, pagesCount, state, permission);
+ }
+
+ return result;
+ }
+ }
+
+ public Result UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected)
+ {
+ ulong pagesCount = pageList.GetPagesCount();
+ ulong size = pagesCount * PageSize;
+
+ ulong endAddr = address + size;
+
+ ulong addrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize;
+
+ if (AddrSpaceStart > address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (addrSpacePagesCount < pagesCount)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (endAddr - 1 > AddrSpaceEnd - 1)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ KPageList currentPageList = new KPageList();
+
+ GetPhysicalRegions(address, size, currentPageList);
+
+ if (!currentPageList.IsEqual(pageList))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.Mask,
+ stateExpected,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState state,
+ out _,
+ out _))
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ Result result = Unmap(address, pagesCount);
+
+ if (result == Result.Success)
+ {
+ _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped);
+ }
+
+ return result;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result MapNormalMemory(long address, long size, KMemoryPermission permission)
+ {
+ // TODO.
+ return Result.Success;
+ }
+
+ public Result MapIoMemory(long address, long size, KMemoryPermission permission)
+ {
+ // TODO.
+ return Result.Success;
+ }
+
+ public Result MapPages(
+ ulong pagesCount,
+ int alignment,
+ ulong srcPa,
+ bool paIsValid,
+ ulong regionStart,
+ ulong regionPagesCount,
+ MemoryState state,
+ KMemoryPermission permission,
+ out ulong address)
+ {
+ address = 0;
+
+ ulong regionSize = regionPagesCount * PageSize;
+
+ if (!CanContain(regionStart, regionSize, state))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (regionPagesCount <= pagesCount)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ lock (_blockManager)
+ {
+ address = AllocateVa(regionStart, regionPagesCount, pagesCount, alignment);
+
+ if (address == 0)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ Result result;
+
+ if (paIsValid)
+ {
+ result = MapPages(address, pagesCount, srcPa, permission, MemoryMapFlags.Private);
+ }
+ else
+ {
+ result = AllocateAndMapPages(address, pagesCount, permission);
+ }
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(address, pagesCount, state, permission);
+ }
+
+ return Result.Success;
+ }
+
+ public Result MapPages(ulong address, ulong pagesCount, MemoryState state, KMemoryPermission permission)
+ {
+ ulong size = pagesCount * PageSize;
+
+ if (!CanContain(address, size, state))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (!IsUnmapped(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ Result result = AllocateAndMapPages(address, pagesCount, permission);
+
+ if (result == Result.Success)
+ {
+ _blockManager.InsertBlock(address, pagesCount, state, permission);
+ }
+
+ return result;
+ }
+ }
+
+ private Result AllocateAndMapPages(ulong address, ulong pagesCount, KMemoryPermission permission)
+ {
+ KMemoryRegionManager region = GetMemoryRegionManager();
+
+ Result result = region.AllocatePages(out KPageList pageList, pagesCount);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
+
+ return MapPages(address, pageList, permission, MemoryMapFlags.Private);
+ }
+
+ public Result MapProcessCodeMemory(ulong dst, ulong src, ulong size)
+ {
+ lock (_blockManager)
+ {
+ bool success = CheckRange(
+ src,
+ size,
+ MemoryState.Mask,
+ MemoryState.Heap,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState state,
+ out KMemoryPermission permission,
+ out _);
+
+ success &= IsUnmapped(dst, size);
+
+ if (success)
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result = MapMemory(src, dst, pagesCount, permission, KMemoryPermission.None);
+
+ _blockManager.InsertBlock(src, pagesCount, state, KMemoryPermission.None, MemoryAttribute.Borrowed);
+ _blockManager.InsertBlock(dst, pagesCount, MemoryState.ModCodeStatic);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result UnmapProcessCodeMemory(ulong dst, ulong src, ulong size)
+ {
+ lock (_blockManager)
+ {
+ bool success = CheckRange(
+ src,
+ size,
+ MemoryState.Mask,
+ MemoryState.Heap,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ success &= CheckRange(
+ dst,
+ PageSize,
+ MemoryState.UnmapProcessCodeMemoryAllowed,
+ MemoryState.UnmapProcessCodeMemoryAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState state,
+ out _,
+ out _);
+
+ success &= CheckRange(
+ dst,
+ size,
+ MemoryState.Mask,
+ state,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None);
+
+ if (success)
+ {
+ ulong pagesCount = size / PageSize;
+
+ Result result = Unmap(dst, pagesCount);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ // TODO: Missing some checks here.
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped);
+ _blockManager.InsertBlock(src, pagesCount, MemoryState.Heap, KMemoryPermission.ReadAndWrite);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result SetHeapSize(ulong size, out ulong address)
+ {
+ address = 0;
+
+ if (size > HeapRegionEnd - HeapRegionStart)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ lock (_blockManager)
+ {
+ ulong currentHeapSize = GetHeapSize();
+
+ if (currentHeapSize <= size)
+ {
+ // Expand.
+ ulong sizeDelta = size - currentHeapSize;
+
+ if (currentProcess.ResourceLimit != null && sizeDelta != 0 &&
+ !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, sizeDelta))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ ulong pagesCount = sizeDelta / PageSize;
+
+ KMemoryRegionManager region = GetMemoryRegionManager();
+
+ Result result = region.AllocatePages(out KPageList pageList, pagesCount);
+
+ using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
+
+ void CleanUpForError()
+ {
+ if (currentProcess.ResourceLimit != null && sizeDelta != 0)
+ {
+ currentProcess.ResourceLimit.Release(LimitableResource.Memory, sizeDelta);
+ }
+ }
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ CleanUpForError();
+
+ return KernelResult.OutOfResource;
+ }
+
+ if (!IsUnmapped(_currentHeapAddr, sizeDelta))
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ result = MapPages(_currentHeapAddr, pageList, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private, true, (byte)_heapFillValue);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ _blockManager.InsertBlock(_currentHeapAddr, pagesCount, MemoryState.Heap, KMemoryPermission.ReadAndWrite);
+ }
+ else
+ {
+ // Shrink.
+ ulong freeAddr = HeapRegionStart + size;
+ ulong sizeDelta = currentHeapSize - size;
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ if (!CheckRange(
+ freeAddr,
+ sizeDelta,
+ MemoryState.Mask,
+ MemoryState.Heap,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong pagesCount = sizeDelta / PageSize;
+
+ Result result = Unmap(freeAddr, pagesCount);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ currentProcess.ResourceLimit?.Release(LimitableResource.Memory, sizeDelta);
+
+ _blockManager.InsertBlock(freeAddr, pagesCount, MemoryState.Unmapped);
+ }
+
+ _currentHeapAddr = HeapRegionStart + size;
+ }
+
+ address = HeapRegionStart;
+
+ return Result.Success;
+ }
+
+ public Result SetMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
+ {
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.PermissionChangeAllowed,
+ MemoryState.PermissionChangeAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState oldState,
+ out KMemoryPermission oldPermission,
+ out _))
+ {
+ if (permission != oldPermission)
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result = Reprotect(address, pagesCount, permission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(address, pagesCount, oldState, permission);
+ }
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public ulong GetTotalHeapSize()
+ {
+ lock (_blockManager)
+ {
+ return GetHeapSize() + PhysicalMemoryUsage;
+ }
+ }
+
+ private ulong GetHeapSize()
+ {
+ return _currentHeapAddr - HeapRegionStart;
+ }
+
+ public Result SetHeapCapacity(ulong capacity)
+ {
+ lock (_blockManager)
+ {
+ _heapCapacity = capacity;
+ }
+
+ return Result.Success;
+ }
+
+ public Result SetMemoryAttribute(
+ ulong address,
+ ulong size,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeValue)
+ {
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.AttributeChangeAllowed,
+ MemoryState.AttributeChangeAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.BorrowedAndIpcMapped,
+ MemoryAttribute.None,
+ MemoryAttribute.DeviceMappedAndUncached,
+ out MemoryState state,
+ out KMemoryPermission permission,
+ out MemoryAttribute attribute))
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ attribute &= ~attributeMask;
+ attribute |= attributeMask & attributeValue;
+
+ _blockManager.InsertBlock(address, pagesCount, state, permission, attribute);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public KMemoryInfo QueryMemory(ulong address)
+ {
+ if (address >= AddrSpaceStart &&
+ address < AddrSpaceEnd)
+ {
+ lock (_blockManager)
+ {
+ return _blockManager.FindBlock(address).GetInfo();
+ }
+ }
+ else
+ {
+ return new KMemoryInfo(
+ AddrSpaceEnd,
+ ~AddrSpaceEnd + 1,
+ MemoryState.Reserved,
+ KMemoryPermission.None,
+ MemoryAttribute.None,
+ KMemoryPermission.None,
+ 0,
+ 0);
+ }
+ }
+
+ public Result Map(ulong dst, ulong src, ulong size)
+ {
+ bool success;
+
+ lock (_blockManager)
+ {
+ success = CheckRange(
+ src,
+ size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState srcState,
+ out _,
+ out _);
+
+ success &= IsUnmapped(dst, size);
+
+ if (success)
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result = MapMemory(src, dst, pagesCount, KMemoryPermission.ReadAndWrite, KMemoryPermission.ReadAndWrite);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(src, pagesCount, srcState, KMemoryPermission.None, MemoryAttribute.Borrowed);
+ _blockManager.InsertBlock(dst, pagesCount, MemoryState.Stack, KMemoryPermission.ReadAndWrite);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result UnmapForKernel(ulong address, ulong pagesCount, MemoryState stateExpected)
+ {
+ ulong size = pagesCount * PageSize;
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.Mask,
+ stateExpected,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _))
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ Result result = Unmap(address, pagesCount);
+
+ if (result == Result.Success)
+ {
+ _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped);
+ }
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result Unmap(ulong dst, ulong src, ulong size)
+ {
+ bool success;
+
+ lock (_blockManager)
+ {
+ success = CheckRange(
+ src,
+ size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ KMemoryPermission.Mask,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState srcState,
+ out _,
+ out _);
+
+ success &= CheckRange(
+ dst,
+ size,
+ MemoryState.Mask,
+ MemoryState.Stack,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out KMemoryPermission dstPermission,
+ out _);
+
+ if (success)
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result = UnmapMemory(dst, src, pagesCount, dstPermission, KMemoryPermission.ReadAndWrite);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(src, pagesCount, srcState, KMemoryPermission.ReadAndWrite);
+ _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result UnmapProcessMemory(ulong dst, ulong size, KPageTableBase srcPageTable, ulong src)
+ {
+ lock (_blockManager)
+ {
+ lock (srcPageTable._blockManager)
+ {
+ bool success = CheckRange(
+ dst,
+ size,
+ MemoryState.Mask,
+ MemoryState.ProcessMemory,
+ KMemoryPermission.ReadAndWrite,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ success &= srcPageTable.CheckRange(
+ src,
+ size,
+ MemoryState.MapProcessAllowed,
+ MemoryState.MapProcessAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ if (!success)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KPageList srcPageList = new KPageList();
+ KPageList dstPageList = new KPageList();
+
+ srcPageTable.GetPhysicalRegions(src, size, srcPageList);
+ GetPhysicalRegions(dst, size, dstPageList);
+
+ if (!dstPageList.IsEqual(srcPageList))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result = Unmap(dst, pagesCount);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped);
+
+ return Result.Success;
+ }
+ }
+
+ public Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
+ {
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.ProcessPermissionChangeAllowed,
+ MemoryState.ProcessPermissionChangeAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState oldState,
+ out KMemoryPermission oldPermission,
+ out _))
+ {
+ MemoryState newState = oldState;
+
+ // If writing into the code region is allowed, then we need
+ // to change it to mutable.
+ if ((permission & KMemoryPermission.Write) != 0)
+ {
+ if (oldState == MemoryState.CodeStatic)
+ {
+ newState = MemoryState.CodeMutable;
+ }
+ else if (oldState == MemoryState.ModCodeStatic)
+ {
+ newState = MemoryState.ModCodeMutable;
+ }
+ else
+ {
+ throw new InvalidOperationException($"Memory state \"{oldState}\" not valid for this operation.");
+ }
+ }
+
+ if (newState != oldState || permission != oldPermission)
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong pagesCount = size / PageSize;
+
+ Result result;
+
+ if ((oldPermission & KMemoryPermission.Execute) != 0)
+ {
+ result = ReprotectWithAttributes(address, pagesCount, permission);
+ }
+ else
+ {
+ result = Reprotect(address, pagesCount, permission);
+ }
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _blockManager.InsertBlock(address, pagesCount, newState, permission);
+ }
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result MapPhysicalMemory(ulong address, ulong size)
+ {
+ ulong endAddr = address + size;
+
+ lock (_blockManager)
+ {
+ ulong mappedSize = 0;
+
+ foreach (KMemoryInfo info in IterateOverRange(address, endAddr))
+ {
+ if (info.State != MemoryState.Unmapped)
+ {
+ mappedSize += GetSizeInRange(info, address, endAddr);
+ }
+ }
+
+ if (mappedSize == size)
+ {
+ return Result.Success;
+ }
+
+ ulong remainingSize = size - mappedSize;
+
+ ulong remainingPages = remainingSize / PageSize;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.ResourceLimit != null &&
+ !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ KMemoryRegionManager region = GetMemoryRegionManager();
+
+ Result result = region.AllocatePages(out KPageList pageList, remainingPages);
+
+ using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
+
+ void CleanUpForError()
+ {
+ currentProcess.ResourceLimit?.Release(LimitableResource.Memory, remainingSize);
+ }
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ CleanUpForError();
+
+ return KernelResult.OutOfResource;
+ }
+
+ LinkedListNode<KPageNode> pageListNode = pageList.Nodes.First;
+
+ KPageNode pageNode = pageListNode.Value;
+
+ ulong srcPa = pageNode.Address;
+ ulong srcPaPages = pageNode.PagesCount;
+
+ foreach (KMemoryInfo info in IterateOverRange(address, endAddr))
+ {
+ if (info.State != MemoryState.Unmapped)
+ {
+ continue;
+ }
+
+ ulong blockSize = GetSizeInRange(info, address, endAddr);
+
+ ulong dstVaPages = blockSize / PageSize;
+
+ ulong dstVa = GetAddrInRange(info, address);
+
+ while (dstVaPages > 0)
+ {
+ if (srcPaPages == 0)
+ {
+ pageListNode = pageListNode.Next;
+
+ pageNode = pageListNode.Value;
+
+ srcPa = pageNode.Address;
+ srcPaPages = pageNode.PagesCount;
+ }
+
+ ulong currentPagesCount = Math.Min(srcPaPages, dstVaPages);
+
+ MapPages(dstVa, currentPagesCount, srcPa, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private);
+
+ dstVa += currentPagesCount * PageSize;
+ srcPa += currentPagesCount * PageSize;
+ srcPaPages -= currentPagesCount;
+ dstVaPages -= currentPagesCount;
+ }
+ }
+
+ PhysicalMemoryUsage += remainingSize;
+
+ ulong pagesCount = size / PageSize;
+
+ _blockManager.InsertBlock(
+ address,
+ pagesCount,
+ MemoryState.Unmapped,
+ KMemoryPermission.None,
+ MemoryAttribute.None,
+ MemoryState.Heap,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.None);
+ }
+
+ return Result.Success;
+ }
+
+ public Result UnmapPhysicalMemory(ulong address, ulong size)
+ {
+ ulong endAddr = address + size;
+
+ lock (_blockManager)
+ {
+ // Scan, ensure that the region can be unmapped (all blocks are heap or
+ // already unmapped), fill pages list for freeing memory.
+ ulong heapMappedSize = 0;
+
+ foreach (KMemoryInfo info in IterateOverRange(address, endAddr))
+ {
+ if (info.State == MemoryState.Heap)
+ {
+ if (info.Attribute != MemoryAttribute.None)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong blockSize = GetSizeInRange(info, address, endAddr);
+
+ heapMappedSize += blockSize;
+ }
+ else if (info.State != MemoryState.Unmapped)
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+
+ if (heapMappedSize == 0)
+ {
+ return Result.Success;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ // Try to unmap all the heap mapped memory inside range.
+ Result result = Result.Success;
+
+ foreach (KMemoryInfo info in IterateOverRange(address, endAddr))
+ {
+ if (info.State == MemoryState.Heap)
+ {
+ ulong blockSize = GetSizeInRange(info, address, endAddr);
+ ulong blockAddress = GetAddrInRange(info, address);
+
+ ulong blockPagesCount = blockSize / PageSize;
+
+ result = Unmap(blockAddress, blockPagesCount);
+
+ // The kernel would attempt to remap if this fails, but we don't because:
+ // - The implementation may not support remapping if memory aliasing is not supported on the platform.
+ // - Unmap can't ever fail here anyway.
+ Debug.Assert(result == Result.Success);
+ }
+ }
+
+ if (result == Result.Success)
+ {
+ PhysicalMemoryUsage -= heapMappedSize;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize);
+
+ ulong pagesCount = size / PageSize;
+
+ _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped);
+ }
+
+ return result;
+ }
+ }
+
+ public Result CopyDataToCurrentProcess(
+ ulong dst,
+ ulong size,
+ ulong src,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permission,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected)
+ {
+ // Client -> server.
+ return CopyDataFromOrToCurrentProcess(
+ size,
+ src,
+ dst,
+ stateMask,
+ stateExpected,
+ permission,
+ attributeMask,
+ attributeExpected,
+ toServer: true);
+ }
+
+ public Result CopyDataFromCurrentProcess(
+ ulong dst,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permission,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ ulong src)
+ {
+ // Server -> client.
+ return CopyDataFromOrToCurrentProcess(
+ size,
+ dst,
+ src,
+ stateMask,
+ stateExpected,
+ permission,
+ attributeMask,
+ attributeExpected,
+ toServer: false);
+ }
+
+ private Result CopyDataFromOrToCurrentProcess(
+ ulong size,
+ ulong clientAddress,
+ ulong serverAddress,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permission,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ bool toServer)
+ {
+ if (AddrSpaceStart > clientAddress)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong srcEndAddr = clientAddress + size;
+
+ if (srcEndAddr <= clientAddress || srcEndAddr - 1 > AddrSpaceEnd - 1)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ clientAddress,
+ size,
+ stateMask,
+ stateExpected,
+ permission,
+ permission,
+ attributeMask | MemoryAttribute.Uncached,
+ attributeExpected))
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ while (size > 0)
+ {
+ ulong copySize = 0x100000; // Copy chunck size. Any value will do, moderate sizes are recommended.
+
+ if (copySize > size)
+ {
+ copySize = size;
+ }
+
+ if (toServer)
+ {
+ currentProcess.CpuMemory.Write(serverAddress, GetSpan(clientAddress, (int)copySize));
+ }
+ else
+ {
+ Write(clientAddress, currentProcess.CpuMemory.GetSpan(serverAddress, (int)copySize));
+ }
+
+ serverAddress += copySize;
+ clientAddress += copySize;
+ size -= copySize;
+ }
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result MapBufferFromClientProcess(
+ ulong size,
+ ulong src,
+ KPageTableBase srcPageTable,
+ KMemoryPermission permission,
+ MemoryState state,
+ bool send,
+ out ulong dst)
+ {
+ dst = 0;
+
+ lock (srcPageTable._blockManager)
+ {
+ lock (_blockManager)
+ {
+ Result result = srcPageTable.ReprotectClientProcess(
+ src,
+ size,
+ permission,
+ state,
+ out int blocksNeeded);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ if (!srcPageTable._slabManager.CanAllocate(blocksNeeded))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong srcMapAddress = BitUtils.AlignUp<ulong>(src, PageSize);
+ ulong srcMapEndAddr = BitUtils.AlignDown<ulong>(src + size, PageSize);
+ ulong srcMapSize = srcMapEndAddr - srcMapAddress;
+
+ result = MapPagesFromClientProcess(size, src, permission, state, srcPageTable, send, out ulong va);
+
+ if (result != Result.Success)
+ {
+ if (srcMapEndAddr > srcMapAddress)
+ {
+ srcPageTable.UnmapIpcRestorePermission(src, size, state);
+ }
+
+ return result;
+ }
+
+ if (srcMapAddress < srcMapEndAddr)
+ {
+ KMemoryPermission permissionMask = permission == KMemoryPermission.ReadAndWrite
+ ? KMemoryPermission.None
+ : KMemoryPermission.Read;
+
+ srcPageTable._blockManager.InsertBlock(srcMapAddress, srcMapSize / PageSize, SetIpcMappingPermissions, permissionMask);
+ }
+
+ dst = va;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ private Result ReprotectClientProcess(
+ ulong address,
+ ulong size,
+ KMemoryPermission permission,
+ MemoryState state,
+ out int blocksNeeded)
+ {
+ blocksNeeded = 0;
+
+ if (AddrSpaceStart > address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong endAddr = address + size;
+
+ if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ MemoryState stateMask;
+
+ switch (state)
+ {
+ case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break;
+ case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break;
+ case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break;
+
+ default: return KernelResult.InvalidCombination;
+ }
+
+ KMemoryPermission permissionMask = permission == KMemoryPermission.ReadAndWrite
+ ? KMemoryPermission.None
+ : KMemoryPermission.Read;
+
+ MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached;
+
+ if (state == MemoryState.IpcBuffer0)
+ {
+ attributeMask |= MemoryAttribute.DeviceMapped;
+ }
+
+ ulong addressRounded = BitUtils.AlignUp<ulong>(address, PageSize);
+ ulong addressTruncated = BitUtils.AlignDown<ulong>(address, PageSize);
+ ulong endAddrRounded = BitUtils.AlignUp<ulong>(endAddr, PageSize);
+ ulong endAddrTruncated = BitUtils.AlignDown<ulong>(endAddr, PageSize);
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong visitedSize = 0;
+
+ void CleanUpForError()
+ {
+ if (visitedSize == 0)
+ {
+ return;
+ }
+
+ ulong endAddrVisited = address + visitedSize;
+
+ foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrVisited))
+ {
+ if ((info.Permission & KMemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0)
+ {
+ ulong blockAddress = GetAddrInRange(info, addressRounded);
+ ulong blockSize = GetSizeInRange(info, addressRounded, endAddrVisited);
+
+ ulong blockPagesCount = blockSize / PageSize;
+
+ Result reprotectResult = Reprotect(blockAddress, blockPagesCount, info.Permission);
+ Debug.Assert(reprotectResult == Result.Success);
+ }
+ }
+ }
+
+ // Signal a read for any resources tracking reads in the region, as the other process is likely to use their data.
+ SignalMemoryTracking(addressTruncated, endAddrRounded - addressTruncated, false);
+
+ // Reprotect the aligned pages range on the client to make them inaccessible from the client process.
+ Result result;
+
+ if (addressRounded < endAddrTruncated)
+ {
+ foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrTruncated))
+ {
+ // Check if the block state matches what we expect.
+ if ((info.State & stateMask) != stateMask ||
+ (info.Permission & permission) != permission ||
+ (info.Attribute & attributeMask) != MemoryAttribute.None)
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong blockAddress = GetAddrInRange(info, addressRounded);
+ ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated);
+
+ ulong blockPagesCount = blockSize / PageSize;
+
+ // If the first block starts before the aligned range, it will need to be split.
+ if (info.Address < addressRounded)
+ {
+ blocksNeeded++;
+ }
+
+ // If the last block ends after the aligned range, it will need to be split.
+ if (endAddrTruncated - 1 < info.Address + info.Size - 1)
+ {
+ blocksNeeded++;
+ }
+
+ if ((info.Permission & KMemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0)
+ {
+ result = Reprotect(blockAddress, blockPagesCount, permissionMask);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+ }
+
+ visitedSize += blockSize;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ private Result MapPagesFromClientProcess(
+ ulong size,
+ ulong address,
+ KMemoryPermission permission,
+ MemoryState state,
+ KPageTableBase srcPageTable,
+ bool send,
+ out ulong dst)
+ {
+ dst = 0;
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong endAddr = address + size;
+
+ ulong addressTruncated = BitUtils.AlignDown<ulong>(address, PageSize);
+ ulong addressRounded = BitUtils.AlignUp<ulong>(address, PageSize);
+ ulong endAddrTruncated = BitUtils.AlignDown<ulong>(endAddr, PageSize);
+ ulong endAddrRounded = BitUtils.AlignUp<ulong>(endAddr, PageSize);
+
+ ulong neededSize = endAddrRounded - addressTruncated;
+
+ ulong neededPagesCount = neededSize / PageSize;
+
+ ulong regionPagesCount = (AliasRegionEnd - AliasRegionStart) / PageSize;
+
+ ulong va = 0;
+
+ for (int unit = MappingUnitSizes.Length - 1; unit >= 0 && va == 0; unit--)
+ {
+ int alignment = MappingUnitSizes[unit];
+
+ va = AllocateVa(AliasRegionStart, regionPagesCount, neededPagesCount, alignment);
+ }
+
+ if (va == 0)
+ {
+ return KernelResult.OutOfVaSpace;
+ }
+
+ ulong dstFirstPagePa = 0;
+ ulong dstLastPagePa = 0;
+ ulong currentVa = va;
+
+ using var _ = new OnScopeExit(() =>
+ {
+ if (dstFirstPagePa != 0)
+ {
+ Context.MemoryManager.DecrementPagesReferenceCount(dstFirstPagePa, 1);
+ }
+
+ if (dstLastPagePa != 0)
+ {
+ Context.MemoryManager.DecrementPagesReferenceCount(dstLastPagePa, 1);
+ }
+ });
+
+ void CleanUpForError()
+ {
+ if (currentVa != va)
+ {
+ Unmap(va, (currentVa - va) / PageSize);
+ }
+ }
+
+ // Is the first page address aligned?
+ // If not, allocate a new page and copy the unaligned chunck.
+ if (addressTruncated < addressRounded)
+ {
+ dstFirstPagePa = GetMemoryRegionManager().AllocatePagesContiguous(Context, 1, _aslrDisabled);
+
+ if (dstFirstPagePa == 0)
+ {
+ CleanUpForError();
+
+ return KernelResult.OutOfMemory;
+ }
+ }
+
+ // Is the last page end address aligned?
+ // If not, allocate a new page and copy the unaligned chunck.
+ if (endAddrTruncated < endAddrRounded && (addressTruncated == addressRounded || addressTruncated < endAddrTruncated))
+ {
+ dstLastPagePa = GetMemoryRegionManager().AllocatePagesContiguous(Context, 1, _aslrDisabled);
+
+ if (dstLastPagePa == 0)
+ {
+ CleanUpForError();
+
+ return KernelResult.OutOfMemory;
+ }
+ }
+
+ if (dstFirstPagePa != 0)
+ {
+ ulong firstPageFillAddress = dstFirstPagePa;
+ ulong unusedSizeAfter;
+
+ if (send)
+ {
+ ulong unusedSizeBefore = address - addressTruncated;
+
+ Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue);
+
+ ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
+ var data = srcPageTable.GetSpan(addressTruncated + unusedSizeBefore, (int)copySize);
+
+ Context.Memory.Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data);
+
+ firstPageFillAddress += unusedSizeBefore + copySize;
+
+ unusedSizeAfter = addressRounded > endAddr ? addressRounded - endAddr : 0;
+ }
+ else
+ {
+ unusedSizeAfter = PageSize;
+ }
+
+ if (unusedSizeAfter != 0)
+ {
+ Context.Memory.Fill(GetDramAddressFromPa(firstPageFillAddress), unusedSizeAfter, (byte)_ipcFillValue);
+ }
+
+ Result result = MapPages(currentVa, 1, dstFirstPagePa, permission, MemoryMapFlags.Private);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ currentVa += PageSize;
+ }
+
+ if (endAddrTruncated > addressRounded)
+ {
+ ulong alignedSize = endAddrTruncated - addressRounded;
+
+ Result result;
+
+ if (srcPageTable.Supports4KBPages)
+ {
+ KPageList pageList = new KPageList();
+ srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
+
+ result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None);
+ }
+ else
+ {
+ result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize);
+ }
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ currentVa += alignedSize;
+ }
+
+ if (dstLastPagePa != 0)
+ {
+ ulong lastPageFillAddr = dstLastPagePa;
+ ulong unusedSizeAfter;
+
+ if (send)
+ {
+ ulong copySize = endAddr - endAddrTruncated;
+ var data = srcPageTable.GetSpan(endAddrTruncated, (int)copySize);
+
+ Context.Memory.Write(GetDramAddressFromPa(dstLastPagePa), data);
+
+ lastPageFillAddr += copySize;
+
+ unusedSizeAfter = PageSize - copySize;
+ }
+ else
+ {
+ unusedSizeAfter = PageSize;
+ }
+
+ Context.Memory.Fill(GetDramAddressFromPa(lastPageFillAddr), unusedSizeAfter, (byte)_ipcFillValue);
+
+ Result result = MapPages(currentVa, 1, dstLastPagePa, permission, MemoryMapFlags.Private);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+ }
+
+ _blockManager.InsertBlock(va, neededPagesCount, state, permission);
+
+ dst = va + (address - addressTruncated);
+
+ return Result.Success;
+ }
+
+ public Result UnmapNoAttributeIfStateEquals(ulong address, ulong size, MemoryState state)
+ {
+ if (AddrSpaceStart > address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ ulong endAddr = address + size;
+
+ if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ MemoryState.Mask,
+ state,
+ KMemoryPermission.Read,
+ KMemoryPermission.Read,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _))
+ {
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ ulong addressTruncated = BitUtils.AlignDown<ulong>(address, PageSize);
+ ulong addressRounded = BitUtils.AlignUp<ulong>(address, PageSize);
+ ulong endAddrTruncated = BitUtils.AlignDown<ulong>(endAddr, PageSize);
+ ulong endAddrRounded = BitUtils.AlignUp<ulong>(endAddr, PageSize);
+
+ ulong pagesCount = (endAddrRounded - addressTruncated) / PageSize;
+
+ Result result = Unmap(addressTruncated, pagesCount);
+
+ if (result == Result.Success)
+ {
+ _blockManager.InsertBlock(addressTruncated, pagesCount, MemoryState.Unmapped);
+ }
+
+ return result;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result UnmapIpcRestorePermission(ulong address, ulong size, MemoryState state)
+ {
+ ulong endAddr = address + size;
+
+ ulong addressRounded = BitUtils.AlignUp<ulong>(address, PageSize);
+ ulong addressTruncated = BitUtils.AlignDown<ulong>(address, PageSize);
+ ulong endAddrRounded = BitUtils.AlignUp<ulong>(endAddr, PageSize);
+ ulong endAddrTruncated = BitUtils.AlignDown<ulong>(endAddr, PageSize);
+
+ ulong pagesCount = addressRounded < endAddrTruncated ? (endAddrTruncated - addressRounded) / PageSize : 0;
+
+ if (pagesCount == 0)
+ {
+ return Result.Success;
+ }
+
+ MemoryState stateMask;
+
+ switch (state)
+ {
+ case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break;
+ case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break;
+ case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break;
+
+ default: return KernelResult.InvalidCombination;
+ }
+
+ MemoryAttribute attributeMask =
+ MemoryAttribute.Borrowed |
+ MemoryAttribute.IpcMapped |
+ MemoryAttribute.Uncached;
+
+ if (state == MemoryState.IpcBuffer0)
+ {
+ attributeMask |= MemoryAttribute.DeviceMapped;
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ // Anything on the client side should see this memory as modified.
+ SignalMemoryTracking(addressTruncated, endAddrRounded - addressTruncated, true);
+
+ lock (_blockManager)
+ {
+ foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrTruncated))
+ {
+ // Check if the block state matches what we expect.
+ if ((info.State & stateMask) != stateMask ||
+ (info.Attribute & attributeMask) != MemoryAttribute.IpcMapped)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (info.Permission != info.SourcePermission && info.IpcRefCount == 1)
+ {
+ ulong blockAddress = GetAddrInRange(info, addressRounded);
+ ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated);
+
+ ulong blockPagesCount = blockSize / PageSize;
+
+ Result result = Reprotect(blockAddress, blockPagesCount, info.SourcePermission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ }
+
+ _blockManager.InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions);
+
+ return Result.Success;
+ }
+ }
+
+ private static void SetIpcMappingPermissions(KMemoryBlock block, KMemoryPermission permission)
+ {
+ block.SetIpcMappingPermission(permission);
+ }
+
+ private static void RestoreIpcMappingPermissions(KMemoryBlock block, KMemoryPermission permission)
+ {
+ block.RestoreIpcMappingPermission();
+ }
+
+ public Result GetPagesIfStateEquals(
+ ulong address,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permissionMask,
+ KMemoryPermission permissionExpected,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ KPageList pageList)
+ {
+ if (!InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ stateMask | MemoryState.IsPoolAllocated,
+ stateExpected | MemoryState.IsPoolAllocated,
+ permissionMask,
+ permissionExpected,
+ attributeMask,
+ attributeExpected,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _))
+ {
+ GetPhysicalRegions(address, size, pageList);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result BorrowIpcBuffer(ulong address, ulong size)
+ {
+ return SetAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.IpcBufferAllowed,
+ MemoryState.IpcBufferAllowed,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Borrowed);
+ }
+
+ public Result BorrowTransferMemory(KPageList pageList, ulong address, ulong size, KMemoryPermission permission)
+ {
+ return SetAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.TransferMemoryAllowed,
+ MemoryState.TransferMemoryAllowed,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ permission,
+ MemoryAttribute.Borrowed,
+ pageList);
+ }
+
+ public Result BorrowCodeMemory(KPageList pageList, ulong address, ulong size)
+ {
+ return SetAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.CodeMemoryAllowed,
+ MemoryState.CodeMemoryAllowed,
+ KMemoryPermission.Mask,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Borrowed,
+ pageList);
+ }
+
+ private Result SetAttributesAndChangePermission(
+ ulong address,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permissionMask,
+ KMemoryPermission permissionExpected,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ KMemoryPermission newPermission,
+ MemoryAttribute attributeSetMask,
+ KPageList pageList = null)
+ {
+ if (address + size <= address || !InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ stateMask | MemoryState.IsPoolAllocated,
+ stateExpected | MemoryState.IsPoolAllocated,
+ permissionMask,
+ permissionExpected,
+ attributeMask,
+ attributeExpected,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState oldState,
+ out KMemoryPermission oldPermission,
+ out MemoryAttribute oldAttribute))
+ {
+ ulong pagesCount = size / PageSize;
+
+ if (pageList != null)
+ {
+ GetPhysicalRegions(address, pagesCount * PageSize, pageList);
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ if (newPermission == KMemoryPermission.None)
+ {
+ newPermission = oldPermission;
+ }
+
+ if (newPermission != oldPermission)
+ {
+ Result result = Reprotect(address, pagesCount, newPermission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+
+ MemoryAttribute newAttribute = oldAttribute | attributeSetMask;
+
+ _blockManager.InsertBlock(address, pagesCount, oldState, newPermission, newAttribute);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ public Result UnborrowIpcBuffer(ulong address, ulong size)
+ {
+ return ClearAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.IpcBufferAllowed,
+ MemoryState.IpcBufferAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Borrowed);
+ }
+
+ public Result UnborrowTransferMemory(ulong address, ulong size, KPageList pageList)
+ {
+ return ClearAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.TransferMemoryAllowed,
+ MemoryState.TransferMemoryAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Borrowed,
+ pageList);
+ }
+
+ public Result UnborrowCodeMemory(ulong address, ulong size, KPageList pageList)
+ {
+ return ClearAttributesAndChangePermission(
+ address,
+ size,
+ MemoryState.CodeMemoryAllowed,
+ MemoryState.CodeMemoryAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ KMemoryPermission.ReadAndWrite,
+ MemoryAttribute.Borrowed,
+ pageList);
+ }
+
+ private Result ClearAttributesAndChangePermission(
+ ulong address,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permissionMask,
+ KMemoryPermission permissionExpected,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ KMemoryPermission newPermission,
+ MemoryAttribute attributeClearMask,
+ KPageList pageList = null)
+ {
+ if (address + size <= address || !InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ lock (_blockManager)
+ {
+ if (CheckRange(
+ address,
+ size,
+ stateMask | MemoryState.IsPoolAllocated,
+ stateExpected | MemoryState.IsPoolAllocated,
+ permissionMask,
+ permissionExpected,
+ attributeMask,
+ attributeExpected,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState oldState,
+ out KMemoryPermission oldPermission,
+ out MemoryAttribute oldAttribute))
+ {
+ ulong pagesCount = size / PageSize;
+
+ if (pageList != null)
+ {
+ KPageList currentPageList = new KPageList();
+
+ GetPhysicalRegions(address, pagesCount * PageSize, currentPageList);
+
+ if (!currentPageList.IsEqual(pageList))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+ }
+
+ if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion))
+ {
+ return KernelResult.OutOfResource;
+ }
+
+ if (newPermission == KMemoryPermission.None)
+ {
+ newPermission = oldPermission;
+ }
+
+ if (newPermission != oldPermission)
+ {
+ Result result = Reprotect(address, pagesCount, newPermission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+
+ MemoryAttribute newAttribute = oldAttribute & ~attributeClearMask;
+
+ _blockManager.InsertBlock(address, pagesCount, oldState, newPermission, newAttribute);
+
+ return Result.Success;
+ }
+ else
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+ }
+
+ private static ulong GetAddrInRange(KMemoryInfo info, ulong start)
+ {
+ if (info.Address < start)
+ {
+ return start;
+ }
+
+ return info.Address;
+ }
+
+ private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end)
+ {
+ ulong endAddr = info.Size + info.Address;
+ ulong size = info.Size;
+
+ if (info.Address < start)
+ {
+ size -= start - info.Address;
+ }
+
+ if (endAddr > end)
+ {
+ size -= endAddr - end;
+ }
+
+ return size;
+ }
+
+ private bool IsUnmapped(ulong address, ulong size)
+ {
+ return CheckRange(
+ address,
+ size,
+ MemoryState.Mask,
+ MemoryState.Unmapped,
+ KMemoryPermission.Mask,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+ }
+
+ private bool CheckRange(
+ ulong address,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permissionMask,
+ KMemoryPermission permissionExpected,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected,
+ MemoryAttribute attributeIgnoreMask,
+ out MemoryState outState,
+ out KMemoryPermission outPermission,
+ out MemoryAttribute outAttribute)
+ {
+ ulong endAddr = address + size;
+
+ KMemoryBlock currBlock = _blockManager.FindBlock(address);
+
+ KMemoryInfo info = currBlock.GetInfo();
+
+ MemoryState firstState = info.State;
+ KMemoryPermission firstPermission = info.Permission;
+ MemoryAttribute firstAttribute = info.Attribute;
+
+ do
+ {
+ info = currBlock.GetInfo();
+
+ // Check if the block state matches what we expect.
+ if (firstState != info.State ||
+ firstPermission != info.Permission ||
+ (info.Attribute & attributeMask) != attributeExpected ||
+ (firstAttribute | attributeIgnoreMask) != (info.Attribute | attributeIgnoreMask) ||
+ (firstState & stateMask) != stateExpected ||
+ (firstPermission & permissionMask) != permissionExpected)
+ {
+ outState = MemoryState.Unmapped;
+ outPermission = KMemoryPermission.None;
+ outAttribute = MemoryAttribute.None;
+
+ return false;
+ }
+ }
+ while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null);
+
+ outState = firstState;
+ outPermission = firstPermission;
+ outAttribute = firstAttribute & ~attributeIgnoreMask;
+
+ return true;
+ }
+
+ private bool CheckRange(
+ ulong address,
+ ulong size,
+ MemoryState stateMask,
+ MemoryState stateExpected,
+ KMemoryPermission permissionMask,
+ KMemoryPermission permissionExpected,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeExpected)
+ {
+ foreach (KMemoryInfo info in IterateOverRange(address, address + size))
+ {
+ // Check if the block state matches what we expect.
+ if ((info.State & stateMask) != stateExpected ||
+ (info.Permission & permissionMask) != permissionExpected ||
+ (info.Attribute & attributeMask) != attributeExpected)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private IEnumerable<KMemoryInfo> IterateOverRange(ulong start, ulong end)
+ {
+ KMemoryBlock currBlock = _blockManager.FindBlock(start);
+
+ KMemoryInfo info;
+
+ do
+ {
+ info = currBlock.GetInfo();
+
+ yield return info;
+ }
+ while (info.Address + info.Size - 1 < end - 1 && (currBlock = currBlock.Successor) != null);
+ }
+
+ private ulong AllocateVa(ulong regionStart, ulong regionPagesCount, ulong neededPagesCount, int alignment)
+ {
+ ulong address = 0;
+
+ ulong regionEndAddr = regionStart + regionPagesCount * PageSize;
+
+ ulong reservedPagesCount = _isKernel ? 1UL : 4UL;
+
+ if (_aslrEnabled)
+ {
+ ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize;
+
+ ulong remainingPages = regionPagesCount - neededPagesCount;
+
+ ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment;
+
+ for (int attempt = 0; attempt < 8; attempt++)
+ {
+ ulong aslrAddress = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, (ulong)alignment);
+ ulong aslrEndAddr = aslrAddress + totalNeededSize;
+
+ KMemoryInfo info = _blockManager.FindBlock(aslrAddress).GetInfo();
+
+ if (info.State != MemoryState.Unmapped)
+ {
+ continue;
+ }
+
+ ulong currBaseAddr = info.Address + reservedPagesCount * PageSize;
+ ulong currEndAddr = info.Address + info.Size;
+
+ if (aslrAddress >= regionStart &&
+ aslrAddress >= currBaseAddr &&
+ aslrEndAddr - 1 <= regionEndAddr - 1 &&
+ aslrEndAddr - 1 <= currEndAddr - 1)
+ {
+ address = aslrAddress;
+ break;
+ }
+ }
+
+ if (address == 0)
+ {
+ ulong aslrPage = GetRandomValue(0, aslrMaxOffset);
+
+ address = FindFirstFit(
+ regionStart + aslrPage * PageSize,
+ regionPagesCount - aslrPage,
+ neededPagesCount,
+ alignment,
+ 0,
+ reservedPagesCount);
+ }
+ }
+
+ if (address == 0)
+ {
+ address = FindFirstFit(
+ regionStart,
+ regionPagesCount,
+ neededPagesCount,
+ alignment,
+ 0,
+ reservedPagesCount);
+ }
+
+ return address;
+ }
+
+ private ulong FindFirstFit(
+ ulong regionStart,
+ ulong regionPagesCount,
+ ulong neededPagesCount,
+ int alignment,
+ ulong reservedStart,
+ ulong reservedPagesCount)
+ {
+ ulong reservedSize = reservedPagesCount * PageSize;
+
+ ulong totalNeededSize = reservedSize + neededPagesCount * PageSize;
+
+ ulong regionEndAddr = (regionStart + regionPagesCount * PageSize) - 1;
+
+ KMemoryBlock currBlock = _blockManager.FindBlock(regionStart);
+
+ KMemoryInfo info = currBlock.GetInfo();
+
+ while (regionEndAddr >= info.Address)
+ {
+ if (info.State == MemoryState.Unmapped)
+ {
+ ulong currBaseAddr = info.Address <= regionStart ? regionStart : info.Address;
+ ulong currEndAddr = info.Address + info.Size - 1;
+
+ currBaseAddr += reservedSize;
+
+ ulong address = BitUtils.AlignDown<ulong>(currBaseAddr, (ulong)alignment) + reservedStart;
+
+ if (currBaseAddr > address)
+ {
+ address += (ulong)alignment;
+ }
+
+ ulong allocationEndAddr = address + totalNeededSize - 1;
+
+ if (info.Address <= address &&
+ address < allocationEndAddr &&
+ allocationEndAddr <= regionEndAddr &&
+ allocationEndAddr <= currEndAddr)
+ {
+ return address;
+ }
+ }
+
+ currBlock = currBlock.Successor;
+
+ if (currBlock == null)
+ {
+ break;
+ }
+
+ info = currBlock.GetInfo();
+ }
+
+ return 0;
+ }
+
+ public bool CanContain(ulong address, ulong size, MemoryState state)
+ {
+ ulong endAddr = address + size;
+
+ ulong regionBaseAddr = GetBaseAddress(state);
+ ulong regionEndAddr = regionBaseAddr + GetSize(state);
+
+ bool InsideRegion()
+ {
+ return regionBaseAddr <= address &&
+ endAddr > address &&
+ endAddr - 1 <= regionEndAddr - 1;
+ }
+
+ bool OutsideHeapRegion()
+ {
+ return endAddr <= HeapRegionStart || address >= HeapRegionEnd;
+ }
+
+ bool OutsideAliasRegion()
+ {
+ return endAddr <= AliasRegionStart || address >= AliasRegionEnd;
+ }
+
+ switch (state)
+ {
+ case MemoryState.Io:
+ case MemoryState.Normal:
+ case MemoryState.CodeStatic:
+ case MemoryState.CodeMutable:
+ case MemoryState.SharedMemory:
+ case MemoryState.ModCodeStatic:
+ case MemoryState.ModCodeMutable:
+ case MemoryState.Stack:
+ case MemoryState.ThreadLocal:
+ case MemoryState.TransferMemoryIsolated:
+ case MemoryState.TransferMemory:
+ case MemoryState.ProcessMemory:
+ case MemoryState.CodeReadOnly:
+ case MemoryState.CodeWritable:
+ return InsideRegion() && OutsideHeapRegion() && OutsideAliasRegion();
+
+ case MemoryState.Heap:
+ return InsideRegion() && OutsideAliasRegion();
+
+ case MemoryState.IpcBuffer0:
+ case MemoryState.IpcBuffer1:
+ case MemoryState.IpcBuffer3:
+ return InsideRegion() && OutsideHeapRegion();
+
+ case MemoryState.KernelStack:
+ return InsideRegion();
+ }
+
+ throw new ArgumentException($"Invalid state value \"{state}\".");
+ }
+
+ private ulong GetBaseAddress(MemoryState state)
+ {
+ switch (state)
+ {
+ case MemoryState.Io:
+ case MemoryState.Normal:
+ case MemoryState.ThreadLocal:
+ return TlsIoRegionStart;
+
+ case MemoryState.CodeStatic:
+ case MemoryState.CodeMutable:
+ case MemoryState.SharedMemory:
+ case MemoryState.ModCodeStatic:
+ case MemoryState.ModCodeMutable:
+ case MemoryState.TransferMemoryIsolated:
+ case MemoryState.TransferMemory:
+ case MemoryState.ProcessMemory:
+ case MemoryState.CodeReadOnly:
+ case MemoryState.CodeWritable:
+ return GetAddrSpaceBaseAddr();
+
+ case MemoryState.Heap:
+ return HeapRegionStart;
+
+ case MemoryState.IpcBuffer0:
+ case MemoryState.IpcBuffer1:
+ case MemoryState.IpcBuffer3:
+ return AliasRegionStart;
+
+ case MemoryState.Stack:
+ return StackRegionStart;
+
+ case MemoryState.KernelStack:
+ return AddrSpaceStart;
+ }
+
+ throw new ArgumentException($"Invalid state value \"{state}\".");
+ }
+
+ private ulong GetSize(MemoryState state)
+ {
+ switch (state)
+ {
+ case MemoryState.Io:
+ case MemoryState.Normal:
+ case MemoryState.ThreadLocal:
+ return TlsIoRegionEnd - TlsIoRegionStart;
+
+ case MemoryState.CodeStatic:
+ case MemoryState.CodeMutable:
+ case MemoryState.SharedMemory:
+ case MemoryState.ModCodeStatic:
+ case MemoryState.ModCodeMutable:
+ case MemoryState.TransferMemoryIsolated:
+ case MemoryState.TransferMemory:
+ case MemoryState.ProcessMemory:
+ case MemoryState.CodeReadOnly:
+ case MemoryState.CodeWritable:
+ return GetAddrSpaceSize();
+
+ case MemoryState.Heap:
+ return HeapRegionEnd - HeapRegionStart;
+
+ case MemoryState.IpcBuffer0:
+ case MemoryState.IpcBuffer1:
+ case MemoryState.IpcBuffer3:
+ return AliasRegionEnd - AliasRegionStart;
+
+ case MemoryState.Stack:
+ return StackRegionEnd - StackRegionStart;
+
+ case MemoryState.KernelStack:
+ return AddrSpaceEnd - AddrSpaceStart;
+ }
+
+ throw new ArgumentException($"Invalid state value \"{state}\".");
+ }
+
+ public ulong GetAddrSpaceBaseAddr()
+ {
+ if (AddrSpaceWidth == 36 || AddrSpaceWidth == 39)
+ {
+ return 0x8000000;
+ }
+ else if (AddrSpaceWidth == 32)
+ {
+ return 0x200000;
+ }
+ else
+ {
+ throw new InvalidOperationException("Invalid address space width!");
+ }
+ }
+
+ public ulong GetAddrSpaceSize()
+ {
+ if (AddrSpaceWidth == 36)
+ {
+ return 0xff8000000;
+ }
+ else if (AddrSpaceWidth == 39)
+ {
+ return 0x7ff8000000;
+ }
+ else if (AddrSpaceWidth == 32)
+ {
+ return 0xffe00000;
+ }
+ else
+ {
+ throw new InvalidOperationException("Invalid address space width!");
+ }
+ }
+
+ private static ulong GetDramAddressFromPa(ulong pa)
+ {
+ return pa - DramMemoryMap.DramBase;
+ }
+
+ protected KMemoryRegionManager GetMemoryRegionManager()
+ {
+ return Context.MemoryManager.MemoryRegions[(int)_memRegion];
+ }
+
+ public ulong GetMmUsedPages()
+ {
+ lock (_blockManager)
+ {
+ return BitUtils.DivRoundUp<ulong>(GetMmUsedSize(), PageSize);
+ }
+ }
+
+ private ulong GetMmUsedSize()
+ {
+ return (ulong)(_blockManager.BlocksCount * KMemoryBlockSize);
+ }
+
+ public bool IsInvalidRegion(ulong address, ulong size)
+ {
+ return address + size - 1 > GetAddrSpaceBaseAddr() + GetAddrSpaceSize() - 1;
+ }
+
+ public bool InsideAddrSpace(ulong address, ulong size)
+ {
+ return AddrSpaceStart <= address && address + size - 1 <= AddrSpaceEnd - 1;
+ }
+
+ public bool InsideAliasRegion(ulong address, ulong size)
+ {
+ return address + size > AliasRegionStart && AliasRegionEnd > address;
+ }
+
+ public bool InsideHeapRegion(ulong address, ulong size)
+ {
+ return address + size > HeapRegionStart && HeapRegionEnd > address;
+ }
+
+ public bool InsideStackRegion(ulong address, ulong size)
+ {
+ return address + size > StackRegionStart && StackRegionEnd > address;
+ }
+
+ public bool OutsideAliasRegion(ulong address, ulong size)
+ {
+ return AliasRegionStart > address || address + size - 1 > AliasRegionEnd - 1;
+ }
+
+ public bool OutsideAddrSpace(ulong address, ulong size)
+ {
+ return AddrSpaceStart > address || address + size - 1 > AddrSpaceEnd - 1;
+ }
+
+ public bool OutsideStackRegion(ulong address, ulong size)
+ {
+ return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1;
+ }
+
+ /// <summary>
+ /// Gets the host regions that make up the given virtual address region.
+ /// If any part of the virtual region is unmapped, null is returned.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <returns>The host regions</returns>
+ /// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ protected abstract IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size);
+
+ /// <summary>
+ /// Gets the physical regions that make up the given virtual address region.
+ /// If any part of the virtual region is unmapped, null is returned.
+ /// </summary>
+ /// <param name="va">Virtual address of the range</param>
+ /// <param name="size">Size of the range</param>
+ /// <param name="pageList">Page list where the ranges will be added</param>
+ protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList);
+
+ /// <summary>
+ /// Gets a read-only span of data from CPU mapped memory.
+ /// </summary>
+ /// <remarks>
+ /// This may perform a allocation if the data is not contiguous in memory.
+ /// For this reason, the span is read-only, you can't modify the data.
+ /// </remarks>
+ /// <param name="va">Virtual address of the data</param>
+ /// <param name="size">Size of the data</param>
+ /// <param name="tracked">True if read tracking is triggered on the span</param>
+ /// <returns>A read-only span of the data</returns>
+ /// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ protected abstract ReadOnlySpan<byte> GetSpan(ulong va, int size);
+
+ /// <summary>
+ /// Maps a new memory region with the contents of a existing memory region.
+ /// </summary>
+ /// <param name="src">Source memory region where the data will be taken from</param>
+ /// <param name="dst">Destination memory region to map</param>
+ /// <param name="pagesCount">Number of pages to map</param>
+ /// <param name="oldSrcPermission">Current protection of the source memory region</param>
+ /// <param name="newDstPermission">Desired protection for the destination memory region</param>
+ /// <returns>Result of the mapping operation</returns>
+ protected abstract Result MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission);
+
+ /// <summary>
+ /// Unmaps a region of memory that was previously mapped with <see cref="MapMemory"/>.
+ /// </summary>
+ /// <param name="dst">Destination memory region to be unmapped</param>
+ /// <param name="src">Source memory region that was originally remapped</param>
+ /// <param name="pagesCount">Number of pages to unmap</param>
+ /// <param name="oldDstPermission">Current protection of the destination memory region</param>
+ /// <param name="newSrcPermission">Desired protection of the source memory region</param>
+ /// <returns>Result of the unmapping operation</returns>
+ protected abstract Result UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission);
+
+ /// <summary>
+ /// Maps a region of memory into the specified physical memory region.
+ /// </summary>
+ /// <param name="dstVa">Destination virtual address that should be mapped</param>
+ /// <param name="pagesCount">Number of pages to map</param>
+ /// <param name="srcPa">Physical address where the pages should be mapped. May be ignored if aliasing is not supported</param>
+ /// <param name="permission">Permission of the region to be mapped</param>
+ /// <param name="flags">Flags controlling the memory map operation</param>
+ /// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
+ /// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
+ /// <returns>Result of the mapping operation</returns>
+ protected abstract Result MapPages(
+ ulong dstVa,
+ ulong pagesCount,
+ ulong srcPa,
+ KMemoryPermission permission,
+ MemoryMapFlags flags,
+ bool shouldFillPages = false,
+ byte fillValue = 0);
+
+ /// <summary>
+ /// Maps a region of memory into the specified physical memory region.
+ /// </summary>
+ /// <param name="address">Destination virtual address that should be mapped</param>
+ /// <param name="pageList">List of physical memory pages where the pages should be mapped. May be ignored if aliasing is not supported</param>
+ /// <param name="permission">Permission of the region to be mapped</param>
+ /// <param name="flags">Flags controlling the memory map operation</param>
+ /// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
+ /// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
+ /// <returns>Result of the mapping operation</returns>
+ protected abstract Result MapPages(
+ ulong address,
+ KPageList pageList,
+ KMemoryPermission permission,
+ MemoryMapFlags flags,
+ bool shouldFillPages = false,
+ byte fillValue = 0);
+
+ /// <summary>
+ /// Maps pages into an arbitrary host memory location.
+ /// </summary>
+ /// <param name="regions">Host regions to be mapped into the specified virtual memory region</param>
+ /// <param name="va">Destination virtual address of the range on this page table</param>
+ /// <param name="size">Size of the range</param>
+ /// <returns>Result of the mapping operation</returns>
+ protected abstract Result MapForeign(IEnumerable<HostMemoryRange> regions, ulong va, ulong size);
+
+ /// <summary>
+ /// Unmaps a region of memory that was previously mapped with one of the page mapping methods.
+ /// </summary>
+ /// <param name="address">Virtual address of the region to unmap</param>
+ /// <param name="pagesCount">Number of pages to unmap</param>
+ /// <returns>Result of the unmapping operation</returns>
+ protected abstract Result Unmap(ulong address, ulong pagesCount);
+
+ /// <summary>
+ /// Changes the permissions of a given virtual memory region.
+ /// </summary>
+ /// <param name="address">Virtual address of the region to have the permission changes</param>
+ /// <param name="pagesCount">Number of pages to have their permissions changed</param>
+ /// <param name="permission">New permission</param>
+ /// <returns>Result of the permission change operation</returns>
+ protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
+
+ /// <summary>
+ /// Changes the permissions of a given virtual memory region.
+ /// </summary>
+ /// <param name="address">Virtual address of the region to have the permission changes</param>
+ /// <param name="pagesCount">Number of pages to have their permissions changed</param>
+ /// <param name="permission">New permission</param>
+ /// <returns>Result of the permission change operation</returns>
+ protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
+
+ /// <summary>
+ /// Alerts the memory tracking that a given region has been read from or written to.
+ /// This should be called before read/write is performed.
+ /// </summary>
+ /// <param name="va">Virtual address of the region</param>
+ /// <param name="size">Size of the region</param>
+ protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write);
+
+ /// <summary>
+ /// Writes data to CPU mapped memory, with write tracking.
+ /// </summary>
+ /// <param name="va">Virtual address to write the data into</param>
+ /// <param name="data">Data to be written</param>
+ /// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+ protected abstract void Write(ulong va, ReadOnlySpan<byte> data);
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs
new file mode 100644
index 00000000..a0c19f9c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ struct KScopedPageList : IDisposable
+ {
+ private readonly KMemoryManager _manager;
+ private KPageList _pageList;
+
+ public KScopedPageList(KMemoryManager manager, KPageList pageList)
+ {
+ _manager = manager;
+ _pageList = pageList;
+ pageList.IncrementPagesReferenceCount(manager);
+ }
+
+ public void SignalSuccess()
+ {
+ _pageList = null;
+ }
+
+ public void Dispose()
+ {
+ _pageList?.DecrementPagesReferenceCount(_manager);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs
new file mode 100644
index 00000000..5ec3cd72
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs
@@ -0,0 +1,75 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KSharedMemory : KAutoObject
+ {
+ private readonly KPageList _pageList;
+
+ private readonly ulong _ownerPid;
+
+ private readonly KMemoryPermission _ownerPermission;
+ private readonly KMemoryPermission _userPermission;
+
+ public KSharedMemory(
+ KernelContext context,
+ SharedMemoryStorage storage,
+ ulong ownerPid,
+ KMemoryPermission ownerPermission,
+ KMemoryPermission userPermission) : base(context)
+ {
+ _pageList = storage.GetPageList();
+ _ownerPid = ownerPid;
+ _ownerPermission = ownerPermission;
+ _userPermission = userPermission;
+ }
+
+ public Result MapIntoProcess(
+ KPageTableBase memoryManager,
+ ulong address,
+ ulong size,
+ KProcess process,
+ KMemoryPermission permission)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ KMemoryPermission expectedPermission = process.Pid == _ownerPid
+ ? _ownerPermission
+ : _userPermission;
+
+ if (permission != expectedPermission)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ // On platforms with page size > 4 KB, this can fail due to the address not being page aligned,
+ // we can return an error to force the application to retry with a different address.
+
+ try
+ {
+ return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
+ }
+ catch (InvalidMemoryRegionException)
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+
+ public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs
new file mode 100644
index 00000000..9051e84c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KSlabHeap
+ {
+ private LinkedList<ulong> _items;
+
+ public KSlabHeap(ulong pa, ulong itemSize, ulong size)
+ {
+ _items = new LinkedList<ulong>();
+
+ int itemsCount = (int)(size / itemSize);
+
+ for (int index = 0; index < itemsCount; index++)
+ {
+ _items.AddLast(pa);
+
+ pa += itemSize;
+ }
+ }
+
+ public bool TryGetItem(out ulong pa)
+ {
+ lock (_items)
+ {
+ if (_items.First != null)
+ {
+ pa = _items.First.Value;
+
+ _items.RemoveFirst();
+
+ return true;
+ }
+ }
+
+ pa = 0;
+
+ return false;
+ }
+
+ public void Free(ulong pa)
+ {
+ lock (_items)
+ {
+ _items.AddFirst(pa);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs
new file mode 100644
index 00000000..b2449598
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs
@@ -0,0 +1,130 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class KTransferMemory : KAutoObject
+ {
+ private KProcess _creator;
+
+ // TODO: Remove when we no longer need to read it from the owner directly.
+ public KProcess Creator => _creator;
+
+ private readonly KPageList _pageList;
+
+ public ulong Address { get; private set; }
+ public ulong Size { get; private set; }
+
+ public KMemoryPermission Permission { get; private set; }
+
+ private bool _hasBeenInitialized;
+ private bool _isMapped;
+
+ public KTransferMemory(KernelContext context) : base(context)
+ {
+ _pageList = new KPageList();
+ }
+
+ public KTransferMemory(KernelContext context, SharedMemoryStorage storage) : base(context)
+ {
+ _pageList = storage.GetPageList();
+ Permission = KMemoryPermission.ReadAndWrite;
+
+ _hasBeenInitialized = true;
+ _isMapped = false;
+ }
+
+ public Result Initialize(ulong address, ulong size, KMemoryPermission permission)
+ {
+ KProcess creator = KernelStatic.GetCurrentProcess();
+
+ _creator = creator;
+
+ Result result = creator.MemoryManager.BorrowTransferMemory(_pageList, address, size, permission);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ creator.IncrementReferenceCount();
+
+ Permission = permission;
+ Address = address;
+ Size = size;
+ _hasBeenInitialized = true;
+ _isMapped = false;
+
+ return result;
+ }
+
+ public Result MapIntoProcess(
+ KPageTableBase memoryManager,
+ ulong address,
+ ulong size,
+ KProcess process,
+ KMemoryPermission permission)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (permission != Permission || _isMapped)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
+
+ Result result = memoryManager.MapPages(address, _pageList, state, KMemoryPermission.ReadAndWrite);
+
+ if (result == Result.Success)
+ {
+ _isMapped = true;
+ }
+
+ return result;
+ }
+
+ public Result UnmapFromProcess(
+ KPageTableBase memoryManager,
+ ulong address,
+ ulong size,
+ KProcess process)
+ {
+ if (_pageList.GetPagesCount() != BitUtils.DivRoundUp<ulong>(size, (ulong)KPageTableBase.PageSize))
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory;
+
+ Result result = memoryManager.UnmapPages(address, _pageList, state);
+
+ if (result == Result.Success)
+ {
+ _isMapped = false;
+ }
+
+ return result;
+ }
+
+ protected override void Destroy()
+ {
+ if (_hasBeenInitialized)
+ {
+ if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _pageList) != Result.Success)
+ {
+ throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes.");
+ }
+
+ _creator.ResourceLimit?.Release(LimitableResource.TransferMemory, 1);
+ _creator.DecrementReferenceCount();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs
new file mode 100644
index 00000000..42407ffe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ [Flags]
+ enum MemoryAttribute : byte
+ {
+ None = 0,
+ Mask = 0xff,
+
+ Borrowed = 1 << 0,
+ IpcMapped = 1 << 1,
+ DeviceMapped = 1 << 2,
+ Uncached = 1 << 3,
+
+ IpcAndDeviceMapped = IpcMapped | DeviceMapped,
+
+ BorrowedAndIpcMapped = Borrowed | IpcMapped,
+
+ DeviceMappedAndUncached = DeviceMapped | Uncached
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs
new file mode 100644
index 00000000..cdc892fc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ enum MemoryFillValue : byte
+ {
+ Zero = 0,
+ Stack = 0x58,
+ Ipc = 0x59,
+ Heap = 0x5A,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs
new file mode 100644
index 00000000..563b817d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ [Flags]
+ enum KMemoryPermission : uint
+ {
+ None = 0,
+ UserMask = Read | Write | Execute,
+ Mask = uint.MaxValue,
+
+ Read = 1 << 0,
+ Write = 1 << 1,
+ Execute = 1 << 2,
+ DontCare = 1 << 28,
+
+ ReadAndWrite = Read | Write,
+ ReadAndExecute = Read | Execute
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs
new file mode 100644
index 00000000..ad719bde
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ enum MemoryRegion
+ {
+ Application = 0,
+ Applet = 1,
+ Service = 2,
+ NvServices = 3
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs
new file mode 100644
index 00000000..d3b61780
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ [Flags]
+ enum MemoryState : uint
+ {
+ Unmapped = 0x00000000,
+ Io = 0x00002001,
+ Normal = 0x00042002,
+ CodeStatic = 0x00DC7E03,
+ CodeMutable = 0x03FEBD04,
+ Heap = 0x037EBD05,
+ SharedMemory = 0x00402006,
+ ModCodeStatic = 0x00DD7E08,
+ ModCodeMutable = 0x03FFBD09,
+ IpcBuffer0 = 0x005C3C0A,
+ Stack = 0x005C3C0B,
+ ThreadLocal = 0x0040200C,
+ TransferMemoryIsolated = 0x015C3C0D,
+ TransferMemory = 0x005C380E,
+ ProcessMemory = 0x0040380F,
+ Reserved = 0x00000010,
+ IpcBuffer1 = 0x005C3811,
+ IpcBuffer3 = 0x004C2812,
+ KernelStack = 0x00002013,
+ CodeReadOnly = 0x00402214,
+ CodeWritable = 0x00402015,
+ UserMask = 0xff,
+ Mask = 0xffffffff,
+
+ PermissionChangeAllowed = 1 << 8,
+ ForceReadWritableByDebugSyscalls = 1 << 9,
+ IpcSendAllowedType0 = 1 << 10,
+ IpcSendAllowedType3 = 1 << 11,
+ IpcSendAllowedType1 = 1 << 12,
+ ProcessPermissionChangeAllowed = 1 << 14,
+ MapAllowed = 1 << 15,
+ UnmapProcessCodeMemoryAllowed = 1 << 16,
+ TransferMemoryAllowed = 1 << 17,
+ QueryPhysicalAddressAllowed = 1 << 18,
+ MapDeviceAllowed = 1 << 19,
+ MapDeviceAlignedAllowed = 1 << 20,
+ IpcBufferAllowed = 1 << 21,
+ IsPoolAllocated = 1 << 22,
+ MapProcessAllowed = 1 << 23,
+ AttributeChangeAllowed = 1 << 24,
+ CodeMemoryAllowed = 1 << 25
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs
new file mode 100644
index 00000000..c68b7369
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Memory
+{
+ class SharedMemoryStorage
+ {
+ private readonly KernelContext _context;
+ private readonly KPageList _pageList;
+ private readonly ulong _size;
+
+ public SharedMemoryStorage(KernelContext context, KPageList pageList)
+ {
+ _context = context;
+ _pageList = pageList;
+ _size = pageList.GetPagesCount() * KPageTableBase.PageSize;
+
+ foreach (KPageNode pageNode in pageList)
+ {
+ ulong address = pageNode.Address - DramMemoryMap.DramBase;
+ ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
+ context.CommitMemory(address, size);
+ }
+ }
+
+ public void ZeroFill()
+ {
+ for (ulong offset = 0; offset < _size; offset += sizeof(ulong))
+ {
+ GetRef<ulong>(offset) = 0;
+ }
+ }
+
+ public ref T GetRef<T>(ulong offset) where T : unmanaged
+ {
+ if (_pageList.Nodes.Count == 1)
+ {
+ ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase;
+ return ref _context.Memory.GetRef<T>(address + offset);
+ }
+
+ throw new NotImplementedException("Non-contiguous shared memory is not yet supported.");
+ }
+
+ public KPageList GetPageList()
+ {
+ return _pageList;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
new file mode 100644
index 00000000..66d56fe3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
@@ -0,0 +1,22 @@
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ static class CapabilityExtensions
+ {
+ public static CapabilityType GetCapabilityType(this uint cap)
+ {
+ return (CapabilityType)(((cap + 1) & ~cap) - 1);
+ }
+
+ public static uint GetFlag(this CapabilityType type)
+ {
+ return (uint)type + 1;
+ }
+
+ public static uint GetId(this CapabilityType type)
+ {
+ return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
new file mode 100644
index 00000000..51d92316
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ enum CapabilityType : uint
+ {
+ CorePriority = (1u << 3) - 1,
+ SyscallMask = (1u << 4) - 1,
+ MapRange = (1u << 6) - 1,
+ MapIoPage = (1u << 7) - 1,
+ MapRegion = (1u << 10) - 1,
+ InterruptPair = (1u << 11) - 1,
+ ProgramType = (1u << 13) - 1,
+ KernelVersion = (1u << 14) - 1,
+ HandleTable = (1u << 15) - 1,
+ DebugFlags = (1u << 16) - 1,
+
+ Invalid = 0u,
+ Padding = ~0u
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
new file mode 100644
index 00000000..8fee5c0d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs
@@ -0,0 +1,465 @@
+using Ryujinx.HLE.HOS.Diagnostics.Demangler;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.Loaders.Elf;
+using Ryujinx.Memory;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class HleProcessDebugger
+ {
+ private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24;
+
+ private KProcess _owner;
+
+ private class Image
+ {
+ public ulong BaseAddress { get; }
+ public ulong Size { get; }
+ public ulong EndAddress => BaseAddress + Size;
+
+ public ElfSymbol[] Symbols { get; }
+
+ public Image(ulong baseAddress, ulong size, ElfSymbol[] symbols)
+ {
+ BaseAddress = baseAddress;
+ Size = size;
+ Symbols = symbols;
+ }
+ }
+
+ private List<Image> _images;
+
+ private int _loaded;
+
+ public HleProcessDebugger(KProcess owner)
+ {
+ _owner = owner;
+
+ _images = new List<Image>();
+ }
+
+ public string GetGuestStackTrace(KThread thread)
+ {
+ EnsureLoaded();
+
+ var context = thread.Context;
+
+ StringBuilder trace = new StringBuilder();
+
+ trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}");
+
+ void AppendTrace(ulong address)
+ {
+ if (AnalyzePointer(out PointerInfo info, address, thread))
+ {
+ trace.AppendLine($" 0x{address:x16}\t{info.ImageDisplay}\t{info.SubDisplay}");
+ }
+ else
+ {
+ trace.AppendLine($" 0x{address:x16}");
+ }
+ }
+
+ if (context.IsAarch32)
+ {
+ ulong framePointer = context.GetX(11);
+
+ while (framePointer != 0)
+ {
+ if ((framePointer & 3) != 0 ||
+ !_owner.CpuMemory.IsMapped(framePointer) ||
+ !_owner.CpuMemory.IsMapped(framePointer + 4))
+ {
+ break;
+ }
+
+ AppendTrace(_owner.CpuMemory.Read<uint>(framePointer + 4));
+
+ framePointer = _owner.CpuMemory.Read<uint>(framePointer);
+ }
+ }
+ else
+ {
+ ulong framePointer = context.GetX(29);
+
+ while (framePointer != 0)
+ {
+ if ((framePointer & 7) != 0 ||
+ !_owner.CpuMemory.IsMapped(framePointer) ||
+ !_owner.CpuMemory.IsMapped(framePointer + 8))
+ {
+ break;
+ }
+
+ AppendTrace(_owner.CpuMemory.Read<ulong>(framePointer + 8));
+
+ framePointer = _owner.CpuMemory.Read<ulong>(framePointer);
+ }
+ }
+
+ return trace.ToString();
+ }
+
+ public string GetCpuRegisterPrintout(KThread thread)
+ {
+ EnsureLoaded();
+
+ var context = thread.Context;
+
+ StringBuilder sb = new StringBuilder();
+
+ string GetReg(int x)
+ {
+ var v = x == 32 ? context.Pc : context.GetX(x);
+ if (!AnalyzePointer(out PointerInfo info, v, thread))
+ {
+ return $"0x{v:x16}";
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(info.ImageName))
+ {
+ return $"0x{v:x16} ({info.ImageDisplay})\t=> {info.SubDisplay}";
+ }
+ else
+ {
+ return $"0x{v:x16} ({info.SpDisplay})";
+ }
+ }
+ }
+
+ for (int i = 0; i <= 28; i++)
+ {
+ sb.AppendLine($"\tX[{i:d2}]:\t{GetReg(i)}");
+ }
+ sb.AppendLine($"\tFP:\t{GetReg(29)}");
+ sb.AppendLine($"\tLR:\t{GetReg(30)}");
+ sb.AppendLine($"\tSP:\t{GetReg(31)}");
+ sb.AppendLine($"\tPC:\t{GetReg(32)}");
+
+ return sb.ToString();
+ }
+
+ private bool TryGetSubName(Image image, ulong address, out ElfSymbol symbol)
+ {
+ address -= image.BaseAddress;
+
+ int left = 0;
+ int right = image.Symbols.Length - 1;
+
+ while (left <= right)
+ {
+ int size = right - left;
+
+ int middle = left + (size >> 1);
+
+ symbol = image.Symbols[middle];
+
+ ulong endAddr = symbol.Value + symbol.Size;
+
+ if (address >= symbol.Value && address < endAddr)
+ {
+ return true;
+ }
+
+ if (address < symbol.Value)
+ {
+ right = middle - 1;
+ }
+ else
+ {
+ left = middle + 1;
+ }
+ }
+
+ symbol = default;
+
+ return false;
+ }
+
+ struct PointerInfo
+ {
+ public string ImageName;
+ public string SubName;
+
+ public ulong Offset;
+ public ulong SubOffset;
+
+ public string ImageDisplay => $"{ImageName}:0x{Offset:x4}";
+ public string SubDisplay => SubOffset == 0 ? SubName : $"{SubName}:0x{SubOffset:x4}";
+ public string SpDisplay => SubOffset == 0 ? "SP" : $"SP:-0x{SubOffset:x4}";
+ }
+
+ private bool AnalyzePointer(out PointerInfo info, ulong address, KThread thread)
+ {
+ if (AnalyzePointerFromImages(out info, address))
+ {
+ return true;
+ }
+
+ if (AnalyzePointerFromStack(out info, address, thread))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool AnalyzePointerFromImages(out PointerInfo info, ulong address)
+ {
+ info = default;
+
+ Image image = GetImage(address, out int imageIndex);
+
+ if (image == null)
+ {
+ // Value isn't a pointer to a known image...
+ return false;
+ }
+
+ info.Offset = address - image.BaseAddress;
+
+ // Try to find what this pointer is referring to
+ if (TryGetSubName(image, address, out ElfSymbol symbol))
+ {
+ info.SubName = symbol.Name;
+
+ // Demangle string if possible
+ if (info.SubName.StartsWith("_Z"))
+ {
+ info.SubName = Demangler.Parse(info.SubName);
+ }
+ info.SubOffset = info.Offset - symbol.Value;
+ }
+ else
+ {
+ info.SubName = "";
+ }
+
+ info.ImageName = GetGuessedNsoNameFromIndex(imageIndex);
+
+ return true;
+ }
+
+ private bool AnalyzePointerFromStack(out PointerInfo info, ulong address, KThread thread)
+ {
+ info = default;
+
+ ulong sp = thread.Context.GetX(31);
+ var memoryInfo = _owner.MemoryManager.QueryMemory(address);
+ MemoryState memoryState = memoryInfo.State;
+
+ if (!memoryState.HasFlag(MemoryState.Stack)) // Is this pointer within the stack?
+ {
+ return false;
+ }
+
+ info.SubOffset = address - sp;
+
+ return true;
+ }
+
+ private Image GetImage(ulong address, out int index)
+ {
+ lock (_images)
+ {
+ for (index = _images.Count - 1; index >= 0; index--)
+ {
+ if (address >= _images[index].BaseAddress && address < _images[index].EndAddress)
+ {
+ return _images[index];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private string GetGuessedNsoNameFromIndex(int index)
+ {
+ if ((uint)index > 11)
+ {
+ return "???";
+ }
+
+ if (index == 0)
+ {
+ return "rtld";
+ }
+ else if (index == 1)
+ {
+ return "main";
+ }
+ else if (index == GetImagesCount() - 1)
+ {
+ return "sdk";
+ }
+ else
+ {
+ return "subsdk" + (index - 2);
+ }
+ }
+
+ private int GetImagesCount()
+ {
+ lock (_images)
+ {
+ return _images.Count;
+ }
+ }
+
+ private void EnsureLoaded()
+ {
+ if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0)
+ {
+ ScanMemoryForTextSegments();
+ }
+ }
+
+ private void ScanMemoryForTextSegments()
+ {
+ ulong oldAddress = 0;
+ ulong address = 0;
+
+ while (address >= oldAddress)
+ {
+ KMemoryInfo info = _owner.MemoryManager.QueryMemory(address);
+
+ if (info.State == MemoryState.Reserved)
+ {
+ break;
+ }
+
+ if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute)
+ {
+ LoadMod0Symbols(_owner.CpuMemory, info.Address, info.Size);
+ }
+
+ oldAddress = address;
+
+ address = info.Address + info.Size;
+ }
+ }
+
+ private void LoadMod0Symbols(IVirtualMemoryManager memory, ulong textOffset, ulong textSize)
+ {
+ ulong mod0Offset = textOffset + memory.Read<uint>(textOffset + 4);
+
+ if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0)
+ {
+ return;
+ }
+
+ Dictionary<ElfDynamicTag, ulong> dynamic = new Dictionary<ElfDynamicTag, ulong>();
+
+ int mod0Magic = memory.Read<int>(mod0Offset + 0x0);
+
+ if (mod0Magic != Mod0)
+ {
+ return;
+ }
+
+ ulong dynamicOffset = memory.Read<uint>(mod0Offset + 0x4) + mod0Offset;
+ ulong bssStartOffset = memory.Read<uint>(mod0Offset + 0x8) + mod0Offset;
+ ulong bssEndOffset = memory.Read<uint>(mod0Offset + 0xc) + mod0Offset;
+ ulong ehHdrStartOffset = memory.Read<uint>(mod0Offset + 0x10) + mod0Offset;
+ ulong ehHdrEndOffset = memory.Read<uint>(mod0Offset + 0x14) + mod0Offset;
+ ulong modObjOffset = memory.Read<uint>(mod0Offset + 0x18) + mod0Offset;
+
+ bool isAArch32 = memory.Read<ulong>(dynamicOffset) > 0xFFFFFFFF || memory.Read<ulong>(dynamicOffset + 0x10) > 0xFFFFFFFF;
+
+ while (true)
+ {
+ ulong tagVal;
+ ulong value;
+
+ if (isAArch32)
+ {
+ tagVal = memory.Read<uint>(dynamicOffset + 0);
+ value = memory.Read<uint>(dynamicOffset + 4);
+
+ dynamicOffset += 0x8;
+ }
+ else
+ {
+ tagVal = memory.Read<ulong>(dynamicOffset + 0);
+ value = memory.Read<ulong>(dynamicOffset + 8);
+
+ dynamicOffset += 0x10;
+ }
+
+ ElfDynamicTag tag = (ElfDynamicTag)tagVal;
+
+ if (tag == ElfDynamicTag.DT_NULL)
+ {
+ break;
+ }
+
+ dynamic[tag] = value;
+ }
+
+ if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out ulong strTab) ||
+ !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out ulong symTab) ||
+ !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out ulong symEntSize))
+ {
+ return;
+ }
+
+ ulong strTblAddr = textOffset + strTab;
+ ulong symTblAddr = textOffset + symTab;
+
+ List<ElfSymbol> symbols = new List<ElfSymbol>();
+
+ while (symTblAddr < strTblAddr)
+ {
+ ElfSymbol sym = isAArch32 ? GetSymbol32(memory, symTblAddr, strTblAddr) : GetSymbol64(memory, symTblAddr, strTblAddr);
+
+ symbols.Add(sym);
+
+ symTblAddr += symEntSize;
+ }
+
+ lock (_images)
+ {
+ _images.Add(new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray()));
+ }
+ }
+
+ private ElfSymbol GetSymbol64(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
+ {
+ ElfSymbol64 sym = memory.Read<ElfSymbol64>(address);
+
+ uint nameIndex = sym.NameOffset;
+
+ string name = string.Empty;
+
+ for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
+ {
+ name += (char)chr;
+ }
+
+ return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
+ }
+
+ private ElfSymbol GetSymbol32(IVirtualMemoryManager memory, ulong address, ulong strTblAddr)
+ {
+ ElfSymbol32 sym = memory.Read<ElfSymbol32>(address);
+
+ uint nameIndex = sym.NameOffset;
+
+ string name = string.Empty;
+
+ for (int chr; (chr = memory.Read<byte>(strTblAddr + nameIndex++)) != 0;)
+ {
+ name += (char)chr;
+ }
+
+ return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
new file mode 100644
index 00000000..c8063a62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Cpu;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ interface IProcessContext : IDisposable
+ {
+ IVirtualMemoryManager AddressSpace { get; }
+
+ IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
+ void Execute(IExecutionContext context, ulong codeAddress);
+ void InvalidateCacheRegion(ulong address, ulong size);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs
new file mode 100644
index 00000000..0a24a524
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs
@@ -0,0 +1,9 @@
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ interface IProcessContextFactory
+ {
+ IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs
new file mode 100644
index 00000000..104fe578
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KContextIdManager
+ {
+ private const int IdMasksCount = 8;
+
+ private int[] _idMasks;
+
+ private int _nextFreeBitHint;
+
+ public KContextIdManager()
+ {
+ _idMasks = new int[IdMasksCount];
+ }
+
+ public int GetId()
+ {
+ lock (_idMasks)
+ {
+ int id = 0;
+
+ if (!TestBit(_nextFreeBitHint))
+ {
+ id = _nextFreeBitHint;
+ }
+ else
+ {
+ for (int index = 0; index < IdMasksCount; index++)
+ {
+ int mask = _idMasks[index];
+
+ int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask));
+
+ if (firstFreeBit < 32)
+ {
+ int baseBit = index * 32 + 31;
+
+ id = baseBit - firstFreeBit;
+
+ break;
+ }
+ else if (index == IdMasksCount - 1)
+ {
+ throw new InvalidOperationException("Maximum number of Ids reached!");
+ }
+ }
+ }
+
+ _nextFreeBitHint = id + 1;
+
+ SetBit(id);
+
+ return id;
+ }
+ }
+
+ public void PutId(int id)
+ {
+ lock (_idMasks)
+ {
+ ClearBit(id);
+ }
+ }
+
+ private bool TestBit(int bit)
+ {
+ return (_idMasks[_nextFreeBitHint / 32] & (1 << (_nextFreeBitHint & 31))) != 0;
+ }
+
+ private void SetBit(int bit)
+ {
+ _idMasks[_nextFreeBitHint / 32] |= (1 << (_nextFreeBitHint & 31));
+ }
+
+ private void ClearBit(int bit)
+ {
+ _idMasks[_nextFreeBitHint / 32] &= ~(1 << (_nextFreeBitHint & 31));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs
new file mode 100644
index 00000000..b5ca9b5e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KHandleEntry
+ {
+ public KHandleEntry Next { get; set; }
+
+ public int Index { get; private set; }
+
+ public ushort HandleId { get; set; }
+ public KAutoObject Obj { get; set; }
+
+ public KHandleEntry(int index)
+ {
+ Index = index;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
new file mode 100644
index 00000000..50f04e90
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
@@ -0,0 +1,285 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KHandleTable
+ {
+ public const int SelfThreadHandle = (0x1ffff << 15) | 0;
+ public const int SelfProcessHandle = (0x1ffff << 15) | 1;
+
+ private readonly KernelContext _context;
+
+ private KHandleEntry[] _table;
+
+ private KHandleEntry _tableHead;
+ private KHandleEntry _nextFreeEntry;
+
+ private int _activeSlotsCount;
+
+ private uint _size;
+
+ private ushort _idCounter;
+
+ public KHandleTable(KernelContext context)
+ {
+ _context = context;
+ }
+
+ public Result Initialize(uint size)
+ {
+ if (size > 1024)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ if (size < 1)
+ {
+ size = 1024;
+ }
+
+ _size = size;
+
+ _idCounter = 1;
+
+ _table = new KHandleEntry[size];
+
+ _tableHead = new KHandleEntry(0);
+
+ KHandleEntry entry = _tableHead;
+
+ for (int index = 0; index < size; index++)
+ {
+ _table[index] = entry;
+
+ entry.Next = new KHandleEntry(index + 1);
+
+ entry = entry.Next;
+ }
+
+ _table[size - 1].Next = null;
+
+ _nextFreeEntry = _tableHead;
+
+ return Result.Success;
+ }
+
+ public Result GenerateHandle(KAutoObject obj, out int handle)
+ {
+ handle = 0;
+
+ lock (_table)
+ {
+ if (_activeSlotsCount >= _size)
+ {
+ return KernelResult.HandleTableFull;
+ }
+
+ KHandleEntry entry = _nextFreeEntry;
+
+ _nextFreeEntry = entry.Next;
+
+ entry.Obj = obj;
+ entry.HandleId = _idCounter;
+
+ _activeSlotsCount++;
+
+ handle = (_idCounter << 15) | entry.Index;
+
+ obj.IncrementReferenceCount();
+
+ if ((short)(_idCounter + 1) >= 0)
+ {
+ _idCounter++;
+ }
+ else
+ {
+ _idCounter = 1;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result ReserveHandle(out int handle)
+ {
+ handle = 0;
+
+ lock (_table)
+ {
+ if (_activeSlotsCount >= _size)
+ {
+ return KernelResult.HandleTableFull;
+ }
+
+ KHandleEntry entry = _nextFreeEntry;
+
+ _nextFreeEntry = entry.Next;
+
+ _activeSlotsCount++;
+
+ handle = (_idCounter << 15) | entry.Index;
+
+ if ((short)(_idCounter + 1) >= 0)
+ {
+ _idCounter++;
+ }
+ else
+ {
+ _idCounter = 1;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public void CancelHandleReservation(int handle)
+ {
+ int index = (handle >> 0) & 0x7fff;
+
+ lock (_table)
+ {
+ KHandleEntry entry = _table[index];
+
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+
+ _activeSlotsCount--;
+ }
+ }
+
+ public void SetReservedHandleObj(int handle, KAutoObject obj)
+ {
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ lock (_table)
+ {
+ KHandleEntry entry = _table[index];
+
+ entry.Obj = obj;
+ entry.HandleId = (ushort)handleId;
+
+ obj.IncrementReferenceCount();
+ }
+ }
+
+ public bool CloseHandle(int handle)
+ {
+ if ((handle >> 30) != 0 ||
+ handle == SelfThreadHandle ||
+ handle == SelfProcessHandle)
+ {
+ return false;
+ }
+
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ KAutoObject obj = null;
+
+ bool result = false;
+
+ lock (_table)
+ {
+ if (handleId != 0 && index < _size)
+ {
+ KHandleEntry entry = _table[index];
+
+ if ((obj = entry.Obj) != null && entry.HandleId == handleId)
+ {
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+
+ _activeSlotsCount--;
+
+ result = true;
+ }
+ }
+ }
+
+ if (result)
+ {
+ obj.DecrementReferenceCount();
+ }
+
+ return result;
+ }
+
+ public T GetObject<T>(int handle) where T : KAutoObject
+ {
+ int index = (handle >> 0) & 0x7fff;
+ int handleId = (handle >> 15);
+
+ lock (_table)
+ {
+ if ((handle >> 30) == 0 && handleId != 0 && index < _size)
+ {
+ KHandleEntry entry = _table[index];
+
+ if (entry.HandleId == handleId && entry.Obj is T obj)
+ {
+ return obj;
+ }
+ }
+ }
+
+ return default;
+ }
+
+ public KThread GetKThread(int handle)
+ {
+ if (handle == SelfThreadHandle)
+ {
+ return KernelStatic.GetCurrentThread();
+ }
+ else
+ {
+ return GetObject<KThread>(handle);
+ }
+ }
+
+ public KProcess GetKProcess(int handle)
+ {
+ if (handle == SelfProcessHandle)
+ {
+ return KernelStatic.GetCurrentProcess();
+ }
+ else
+ {
+ return GetObject<KProcess>(handle);
+ }
+ }
+
+ public void Destroy()
+ {
+ lock (_table)
+ {
+ for (int index = 0; index < _size; index++)
+ {
+ KHandleEntry entry = _table[index];
+
+ if (entry.Obj != null)
+ {
+ if (entry.Obj is IDisposable disposableObj)
+ {
+ disposableObj.Dispose();
+ }
+
+ entry.Obj.DecrementReferenceCount();
+ entry.Obj = null;
+ entry.Next = _nextFreeEntry;
+
+ _nextFreeEntry = entry;
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
new file mode 100644
index 00000000..21e89944
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -0,0 +1,1196 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KProcess : KSynchronizationObject
+ {
+ public const uint KernelVersionMajor = 10;
+ public const uint KernelVersionMinor = 4;
+ public const uint KernelVersionRevision = 0;
+
+ public const uint KernelVersionPacked =
+ (KernelVersionMajor << 19) |
+ (KernelVersionMinor << 15) |
+ (KernelVersionRevision << 0);
+
+ public KPageTableBase MemoryManager { get; private set; }
+
+ private SortedDictionary<ulong, KTlsPageInfo> _fullTlsPages;
+ private SortedDictionary<ulong, KTlsPageInfo> _freeTlsPages;
+
+ public int DefaultCpuCore { get; set; }
+
+ public bool Debug { get; private set; }
+
+ public KResourceLimit ResourceLimit { get; private set; }
+
+ public ulong PersonalMmHeapPagesCount { get; private set; }
+
+ public ProcessState State { get; private set; }
+
+ private object _processLock;
+ private object _threadingLock;
+
+ public KAddressArbiter AddressArbiter { get; private set; }
+
+ public ulong[] RandomEntropy { get; private set; }
+ public KThread[] PinnedThreads { get; private set; }
+
+ private bool _signaled;
+
+ public string Name { get; private set; }
+
+ private int _threadCount;
+
+ public ProcessCreationFlags Flags { get; private set; }
+
+ private MemoryRegion _memRegion;
+
+ public KProcessCapabilities Capabilities { get; private set; }
+
+ public bool AllowCodeMemoryForJit { get; private set; }
+
+ public ulong TitleId { get; private set; }
+ public bool IsApplication { get; private set; }
+ public ulong Pid { get; private set; }
+
+ private long _creationTimestamp;
+ private ulong _entrypoint;
+ private ThreadStart _customThreadStart;
+ private ulong _imageSize;
+ private ulong _mainThreadStackSize;
+ private ulong _memoryUsageCapacity;
+ private int _version;
+
+ public KHandleTable HandleTable { get; private set; }
+
+ public ulong UserExceptionContextAddress { get; private set; }
+
+ private LinkedList<KThread> _threads;
+
+ public bool IsPaused { get; private set; }
+
+ private long _totalTimeRunning;
+
+ public long TotalTimeRunning => _totalTimeRunning;
+
+ private IProcessContextFactory _contextFactory;
+ public IProcessContext Context { get; private set; }
+ public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
+
+ public HleProcessDebugger Debugger { get; private set; }
+
+ public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
+ {
+ _processLock = new object();
+ _threadingLock = new object();
+
+ AddressArbiter = new KAddressArbiter(context);
+
+ _fullTlsPages = new SortedDictionary<ulong, KTlsPageInfo>();
+ _freeTlsPages = new SortedDictionary<ulong, KTlsPageInfo>();
+
+ Capabilities = new KProcessCapabilities();
+
+ AllowCodeMemoryForJit = allowCodeMemoryForJit;
+
+ RandomEntropy = new ulong[KScheduler.CpuCoresCount];
+ PinnedThreads = new KThread[KScheduler.CpuCoresCount];
+
+ // TODO: Remove once we no longer need to initialize it externally.
+ HandleTable = new KHandleTable(context);
+
+ _threads = new LinkedList<KThread>();
+
+ Debugger = new HleProcessDebugger(this);
+ }
+
+ public Result InitializeKip(
+ ProcessCreationInfo creationInfo,
+ ReadOnlySpan<uint> capabilities,
+ KPageList pageList,
+ KResourceLimit resourceLimit,
+ MemoryRegion memRegion,
+ IProcessContextFactory contextFactory,
+ ThreadStart customThreadStart = null)
+ {
+ ResourceLimit = resourceLimit;
+ _memRegion = memRegion;
+ _contextFactory = contextFactory ?? new ProcessContextFactory();
+ _customThreadStart = customThreadStart;
+
+ AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
+
+ Pid = KernelContext.NewKipId();
+
+ if (Pid == 0 || Pid >= KernelConstants.InitialProcessId)
+ {
+ throw new InvalidOperationException($"Invalid KIP Id {Pid}.");
+ }
+
+ InitializeMemoryManager(creationInfo.Flags);
+
+ bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
+
+ ulong codeAddress = creationInfo.CodeAddress;
+
+ ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
+
+ KMemoryBlockSlabManager slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication)
+ ? KernelContext.LargeMemoryBlockSlabManager
+ : KernelContext.SmallMemoryBlockSlabManager;
+
+ Result result = MemoryManager.InitializeForProcess(
+ addrSpaceType,
+ aslrEnabled,
+ !aslrEnabled,
+ memRegion,
+ codeAddress,
+ codeSize,
+ slabManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ result = MemoryManager.MapPages(codeAddress, pageList, MemoryState.CodeStatic, KMemoryPermission.None);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = Capabilities.InitializeForKernel(capabilities, MemoryManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return ParseProcessInfo(creationInfo);
+ }
+
+ public Result Initialize(
+ ProcessCreationInfo creationInfo,
+ ReadOnlySpan<uint> capabilities,
+ KResourceLimit resourceLimit,
+ MemoryRegion memRegion,
+ IProcessContextFactory contextFactory,
+ ThreadStart customThreadStart = null)
+ {
+ ResourceLimit = resourceLimit;
+ _memRegion = memRegion;
+ _contextFactory = contextFactory ?? new ProcessContextFactory();
+ _customThreadStart = customThreadStart;
+ IsApplication = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication);
+
+ ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
+
+ ulong codePagesCount = (ulong)creationInfo.CodePagesCount;
+
+ ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KPageTableBase.PageSize;
+
+ if (neededSizeForProcess != 0 && resourceLimit != null)
+ {
+ if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+ }
+
+ void CleanUpForError()
+ {
+ if (neededSizeForProcess != 0 && resourceLimit != null)
+ {
+ resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess);
+ }
+ }
+
+ PersonalMmHeapPagesCount = (ulong)creationInfo.SystemResourcePagesCount;
+
+ KMemoryBlockSlabManager slabManager;
+
+ if (PersonalMmHeapPagesCount != 0)
+ {
+ slabManager = new KMemoryBlockSlabManager(PersonalMmHeapPagesCount * KPageTableBase.PageSize);
+ }
+ else
+ {
+ slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication)
+ ? KernelContext.LargeMemoryBlockSlabManager
+ : KernelContext.SmallMemoryBlockSlabManager;
+ }
+
+ AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
+
+ Pid = KernelContext.NewProcessId();
+
+ if (Pid == ulong.MaxValue || Pid < KernelConstants.InitialProcessId)
+ {
+ throw new InvalidOperationException($"Invalid Process Id {Pid}.");
+ }
+
+ InitializeMemoryManager(creationInfo.Flags);
+
+ bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
+
+ ulong codeAddress = creationInfo.CodeAddress;
+
+ ulong codeSize = codePagesCount * KPageTableBase.PageSize;
+
+ Result result = MemoryManager.InitializeForProcess(
+ addrSpaceType,
+ aslrEnabled,
+ !aslrEnabled,
+ memRegion,
+ codeAddress,
+ codeSize,
+ slabManager);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic))
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidMemRange;
+ }
+
+ result = MemoryManager.MapPages(
+ codeAddress,
+ codePagesCount,
+ MemoryState.CodeStatic,
+ KMemoryPermission.None);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = Capabilities.InitializeForUser(capabilities, MemoryManager);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = ParseProcessInfo(creationInfo);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+ }
+
+ return result;
+ }
+
+ private Result ParseProcessInfo(ProcessCreationInfo creationInfo)
+ {
+ // Ensure that the current kernel version is equal or above to the minimum required.
+ uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19;
+ uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf;
+
+ if (KernelContext.EnableVersionChecks)
+ {
+ if (requiredKernelVersionMajor > KernelVersionMajor)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (requiredKernelVersionMinor > KernelVersionMinor)
+ {
+ return KernelResult.InvalidCombination;
+ }
+ }
+
+ Result result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ UserExceptionContextAddress = userExceptionContextAddress;
+
+ MemoryHelper.FillWithZeros(CpuMemory, userExceptionContextAddress, KTlsPageInfo.TlsEntrySize);
+
+ Name = creationInfo.Name;
+
+ State = ProcessState.Created;
+
+ _creationTimestamp = PerformanceCounter.ElapsedMilliseconds;
+
+ Flags = creationInfo.Flags;
+ _version = creationInfo.Version;
+ TitleId = creationInfo.TitleId;
+ _entrypoint = creationInfo.CodeAddress;
+ _imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
+
+ switch (Flags & ProcessCreationFlags.AddressSpaceMask)
+ {
+ case ProcessCreationFlags.AddressSpace32Bit:
+ case ProcessCreationFlags.AddressSpace64BitDeprecated:
+ case ProcessCreationFlags.AddressSpace64Bit:
+ _memoryUsageCapacity = MemoryManager.HeapRegionEnd -
+ MemoryManager.HeapRegionStart;
+ break;
+
+ case ProcessCreationFlags.AddressSpace32BitWithoutAlias:
+ _memoryUsageCapacity = MemoryManager.HeapRegionEnd -
+ MemoryManager.HeapRegionStart +
+ MemoryManager.AliasRegionEnd -
+ MemoryManager.AliasRegionStart;
+ break;
+
+ default: throw new InvalidOperationException($"Invalid MMU flags value 0x{Flags:x2}.");
+ }
+
+ GenerateRandomEntropy();
+
+ return Result.Success;
+ }
+
+ public Result AllocateThreadLocalStorage(out ulong address)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ Result result;
+
+ if (_freeTlsPages.Count > 0)
+ {
+ // If we have free TLS pages available, just use the first one.
+ KTlsPageInfo pageInfo = _freeTlsPages.Values.First();
+
+ if (!pageInfo.TryGetFreePage(out address))
+ {
+ throw new InvalidOperationException("Unexpected failure getting free TLS page!");
+ }
+
+ if (pageInfo.IsFull())
+ {
+ _freeTlsPages.Remove(pageInfo.PageVirtualAddress);
+
+ _fullTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo);
+ }
+
+ result = Result.Success;
+ }
+ else
+ {
+ // Otherwise, we need to create a new one.
+ result = AllocateTlsPage(out KTlsPageInfo pageInfo);
+
+ if (result == Result.Success)
+ {
+ if (!pageInfo.TryGetFreePage(out address))
+ {
+ throw new InvalidOperationException("Unexpected failure getting free TLS page!");
+ }
+
+ _freeTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo);
+ }
+ else
+ {
+ address = 0;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private Result AllocateTlsPage(out KTlsPageInfo pageInfo)
+ {
+ pageInfo = default;
+
+ if (!KernelContext.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa))
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ ulong regionStart = MemoryManager.TlsIoRegionStart;
+ ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart;
+
+ ulong regionPagesCount = regionSize / KPageTableBase.PageSize;
+
+ Result result = MemoryManager.MapPages(
+ 1,
+ KPageTableBase.PageSize,
+ tlsPagePa,
+ true,
+ regionStart,
+ regionPagesCount,
+ MemoryState.ThreadLocal,
+ KMemoryPermission.ReadAndWrite,
+ out ulong tlsPageVa);
+
+ if (result != Result.Success)
+ {
+ KernelContext.UserSlabHeapPages.Free(tlsPagePa);
+ }
+ else
+ {
+ pageInfo = new KTlsPageInfo(tlsPageVa, tlsPagePa);
+
+ MemoryHelper.FillWithZeros(CpuMemory, tlsPageVa, KPageTableBase.PageSize);
+ }
+
+ return result;
+ }
+
+ public Result FreeThreadLocalStorage(ulong tlsSlotAddr)
+ {
+ ulong tlsPageAddr = BitUtils.AlignDown<ulong>(tlsSlotAddr, KPageTableBase.PageSize);
+
+ KernelContext.CriticalSection.Enter();
+
+ Result result = Result.Success;
+
+ KTlsPageInfo pageInfo;
+
+ if (_fullTlsPages.TryGetValue(tlsPageAddr, out pageInfo))
+ {
+ // TLS page was full, free slot and move to free pages tree.
+ _fullTlsPages.Remove(tlsPageAddr);
+
+ _freeTlsPages.Add(tlsPageAddr, pageInfo);
+ }
+ else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo))
+ {
+ result = KernelResult.InvalidAddress;
+ }
+
+ if (pageInfo != null)
+ {
+ pageInfo.FreeTlsSlot(tlsSlotAddr);
+
+ if (pageInfo.IsEmpty())
+ {
+ // TLS page is now empty, we should ensure it is removed
+ // from all trees, and free the memory it was using.
+ _freeTlsPages.Remove(tlsPageAddr);
+
+ KernelContext.CriticalSection.Leave();
+
+ FreeTlsPage(pageInfo);
+
+ return Result.Success;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private Result FreeTlsPage(KTlsPageInfo pageInfo)
+ {
+ Result result = MemoryManager.UnmapForKernel(pageInfo.PageVirtualAddress, 1, MemoryState.ThreadLocal);
+
+ if (result == Result.Success)
+ {
+ KernelContext.UserSlabHeapPages.Free(pageInfo.PagePhysicalAddress);
+ }
+
+ return result;
+ }
+
+ private void GenerateRandomEntropy()
+ {
+ // TODO.
+ }
+
+ public Result Start(int mainThreadPriority, ulong stackSize)
+ {
+ lock (_processLock)
+ {
+ if (State > ProcessState.CreatedAttached)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ KResourceLimit threadResourceLimit = ResourceLimit;
+ KResourceLimit memoryResourceLimit = null;
+
+ if (_mainThreadStackSize != 0)
+ {
+ throw new InvalidOperationException("Trying to start a process with a invalid state!");
+ }
+
+ ulong stackSizeRounded = BitUtils.AlignUp<ulong>(stackSize, KPageTableBase.PageSize);
+
+ ulong neededSize = stackSizeRounded + _imageSize;
+
+ // Check if the needed size for the code and the stack will fit on the
+ // memory usage capacity of this Process. Also check for possible overflow
+ // on the above addition.
+ if (neededSize > _memoryUsageCapacity || neededSize < stackSizeRounded)
+ {
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+
+ return KernelResult.OutOfMemory;
+ }
+
+ if (stackSizeRounded != 0 && ResourceLimit != null)
+ {
+ memoryResourceLimit = ResourceLimit;
+
+ if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded))
+ {
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+
+ return KernelResult.ResLimitExceeded;
+ }
+ }
+
+ Result result;
+
+ KThread mainThread = null;
+
+ ulong stackTop = 0;
+
+ void CleanUpForError()
+ {
+ HandleTable.Destroy();
+
+ mainThread?.DecrementReferenceCount();
+
+ if (_mainThreadStackSize != 0)
+ {
+ ulong stackBottom = stackTop - _mainThreadStackSize;
+
+ ulong stackPagesCount = _mainThreadStackSize / KPageTableBase.PageSize;
+
+ MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack);
+
+ _mainThreadStackSize = 0;
+ }
+
+ memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded);
+ threadResourceLimit?.Release(LimitableResource.Thread, 1);
+ }
+
+ if (stackSizeRounded != 0)
+ {
+ ulong stackPagesCount = stackSizeRounded / KPageTableBase.PageSize;
+
+ ulong regionStart = MemoryManager.StackRegionStart;
+ ulong regionSize = MemoryManager.StackRegionEnd - regionStart;
+
+ ulong regionPagesCount = regionSize / KPageTableBase.PageSize;
+
+ result = MemoryManager.MapPages(
+ stackPagesCount,
+ KPageTableBase.PageSize,
+ 0,
+ false,
+ regionStart,
+ regionPagesCount,
+ MemoryState.Stack,
+ KMemoryPermission.ReadAndWrite,
+ out ulong stackBottom);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ _mainThreadStackSize += stackSizeRounded;
+
+ stackTop = stackBottom + stackSizeRounded;
+ }
+
+ ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize;
+
+ result = MemoryManager.SetHeapCapacity(heapCapacity);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ HandleTable = new KHandleTable(KernelContext);
+
+ result = HandleTable.Initialize(Capabilities.HandleTableSize);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ mainThread = new KThread(KernelContext);
+
+ result = mainThread.Initialize(
+ _entrypoint,
+ 0,
+ stackTop,
+ mainThreadPriority,
+ DefaultCpuCore,
+ this,
+ ThreadType.User,
+ _customThreadStart);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ mainThread.SetEntryArguments(0, mainThreadHandle);
+
+ ProcessState oldState = State;
+ ProcessState newState = State != ProcessState.Created
+ ? ProcessState.Attached
+ : ProcessState.Started;
+
+ SetState(newState);
+
+ result = mainThread.Start();
+
+ if (result != Result.Success)
+ {
+ SetState(oldState);
+
+ CleanUpForError();
+ }
+
+ if (result == Result.Success)
+ {
+ mainThread.IncrementReferenceCount();
+ }
+
+ mainThread.DecrementReferenceCount();
+
+ return result;
+ }
+ }
+
+ private void SetState(ProcessState newState)
+ {
+ if (State != newState)
+ {
+ State = newState;
+ _signaled = true;
+
+ Signal();
+ }
+ }
+
+ public Result InitializeThread(
+ KThread thread,
+ ulong entrypoint,
+ ulong argsPtr,
+ ulong stackTop,
+ int priority,
+ int cpuCore,
+ ThreadStart customThreadStart = null)
+ {
+ lock (_processLock)
+ {
+ return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, customThreadStart);
+ }
+ }
+
+ public IExecutionContext CreateExecutionContext()
+ {
+ return Context?.CreateExecutionContext(new ExceptionCallbacks(
+ InterruptHandler,
+ null,
+ KernelContext.SyscallHandler.SvcCall,
+ UndefinedInstructionHandler));
+ }
+
+ private void InterruptHandler(IExecutionContext context)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.Context.Running &&
+ currentThread.Owner != null &&
+ currentThread.GetUserDisableCount() != 0 &&
+ currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ currentThread.Owner.PinThread(currentThread);
+
+ currentThread.SetUserInterruptFlag();
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ if (currentThread.IsSchedulable)
+ {
+ KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
+ }
+
+ currentThread.HandlePostSyscall();
+ }
+
+ public void IncrementThreadCount()
+ {
+ Interlocked.Increment(ref _threadCount);
+ }
+
+ public void DecrementThreadCountAndTerminateIfZero()
+ {
+ if (Interlocked.Decrement(ref _threadCount) == 0)
+ {
+ Terminate();
+ }
+ }
+
+ public void DecrementToZeroWhileTerminatingCurrent()
+ {
+ while (Interlocked.Decrement(ref _threadCount) != 0)
+ {
+ Destroy();
+ TerminateCurrentProcess();
+ }
+
+ // Nintendo panic here because if it reaches this point, the current thread should be already dead.
+ // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here.
+ }
+
+ public ulong GetMemoryCapacity()
+ {
+ ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory);
+
+ totalCapacity += MemoryManager.GetTotalHeapSize();
+
+ totalCapacity += GetPersonalMmHeapSize();
+
+ totalCapacity += _imageSize + _mainThreadStackSize;
+
+ if (totalCapacity <= _memoryUsageCapacity)
+ {
+ return totalCapacity;
+ }
+
+ return _memoryUsageCapacity;
+ }
+
+ public ulong GetMemoryUsage()
+ {
+ return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize();
+ }
+
+ public ulong GetMemoryCapacityWithoutPersonalMmHeap()
+ {
+ return GetMemoryCapacity() - GetPersonalMmHeapSize();
+ }
+
+ public ulong GetMemoryUsageWithoutPersonalMmHeap()
+ {
+ return GetMemoryUsage() - GetPersonalMmHeapSize();
+ }
+
+ private ulong GetPersonalMmHeapSize()
+ {
+ return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion);
+ }
+
+ private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion)
+ {
+ if (memRegion == MemoryRegion.Applet)
+ {
+ return 0;
+ }
+
+ return personalMmHeapPagesCount * KPageTableBase.PageSize;
+ }
+
+ public void AddCpuTime(long ticks)
+ {
+ Interlocked.Add(ref _totalTimeRunning, ticks);
+ }
+
+ public void AddThread(KThread thread)
+ {
+ lock (_threadingLock)
+ {
+ thread.ProcessListNode = _threads.AddLast(thread);
+ }
+ }
+
+ public void RemoveThread(KThread thread)
+ {
+ lock (_threadingLock)
+ {
+ _threads.Remove(thread.ProcessListNode);
+ }
+ }
+
+ public bool IsCpuCoreAllowed(int core)
+ {
+ return (Capabilities.AllowedCpuCoresMask & (1UL << core)) != 0;
+ }
+
+ public bool IsPriorityAllowed(int priority)
+ {
+ return (Capabilities.AllowedThreadPriosMask & (1UL << priority)) != 0;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _signaled;
+ }
+
+ public Result Terminate()
+ {
+ Result result;
+
+ bool shallTerminate = false;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State >= ProcessState.Started)
+ {
+ if (State == ProcessState.Started ||
+ State == ProcessState.Crashed ||
+ State == ProcessState.Attached ||
+ State == ProcessState.DebugSuspended)
+ {
+ SetState(ProcessState.Exiting);
+
+ shallTerminate = true;
+ }
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (shallTerminate)
+ {
+ UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
+
+ HandleTable.Destroy();
+
+ SignalExitToDebugTerminated();
+ SignalExit();
+ }
+
+ return result;
+ }
+
+ public void TerminateCurrentProcess()
+ {
+ bool shallTerminate = false;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State >= ProcessState.Started)
+ {
+ if (State == ProcessState.Started ||
+ State == ProcessState.Attached ||
+ State == ProcessState.DebugSuspended)
+ {
+ SetState(ProcessState.Exiting);
+
+ shallTerminate = true;
+ }
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (shallTerminate)
+ {
+ UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
+
+ HandleTable.Destroy();
+
+ // NOTE: this is supposed to be called in receiving of the mailbox.
+ SignalExitToDebugExited();
+ SignalExit();
+ }
+
+ KernelStatic.GetCurrentThread().Exit();
+ }
+
+ private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
+ {
+ lock (_threadingLock)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread)
+ {
+ UnpinThread(currentThread);
+ }
+
+ foreach (KThread thread in _threads)
+ {
+ if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
+ {
+ thread.PrepareForTermination();
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ while (true)
+ {
+ KThread blockedThread = null;
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
+ {
+ thread.IncrementReferenceCount();
+
+ blockedThread = thread;
+ break;
+ }
+ }
+ }
+
+ if (blockedThread == null)
+ {
+ break;
+ }
+
+ blockedThread.Terminate();
+ blockedThread.DecrementReferenceCount();
+ }
+ }
+
+ private void SignalExitToDebugTerminated()
+ {
+ // TODO: Debug events.
+ }
+
+ private void SignalExitToDebugExited()
+ {
+ // TODO: Debug events.
+ }
+
+ private void SignalExit()
+ {
+ if (ResourceLimit != null)
+ {
+ ResourceLimit.Release(LimitableResource.Memory, GetMemoryUsage());
+ }
+
+ KernelContext.CriticalSection.Enter();
+
+ SetState(ProcessState.Exited);
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public Result ClearIfNotExited()
+ {
+ Result result;
+
+ KernelContext.CriticalSection.Enter();
+
+ lock (_processLock)
+ {
+ if (State != ProcessState.Exited && _signaled)
+ {
+ _signaled = false;
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ private void InitializeMemoryManager(ProcessCreationFlags flags)
+ {
+ int addrSpaceBits = (flags & ProcessCreationFlags.AddressSpaceMask) switch
+ {
+ ProcessCreationFlags.AddressSpace32Bit => 32,
+ ProcessCreationFlags.AddressSpace64BitDeprecated => 36,
+ ProcessCreationFlags.AddressSpace32BitWithoutAlias => 32,
+ ProcessCreationFlags.AddressSpace64Bit => 39,
+ _ => 39
+ };
+
+ bool for64Bit = flags.HasFlag(ProcessCreationFlags.Is64Bit);
+
+ Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit);
+
+ MemoryManager = new KPageTable(KernelContext, CpuMemory);
+ }
+
+ private bool InvalidAccessHandler(ulong va)
+ {
+ KernelStatic.GetCurrentThread()?.PrintGuestStackTrace();
+ KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
+
+ Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
+
+ return false;
+ }
+
+ private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode)
+ {
+ KernelStatic.GetCurrentThread().PrintGuestStackTrace();
+ KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout();
+
+ throw new UndefinedInstructionException(address, opCode);
+ }
+
+ protected override void Destroy() => Context.Dispose();
+
+ public Result SetActivity(bool pause)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (State != ProcessState.Exiting && State != ProcessState.Exited)
+ {
+ if (pause)
+ {
+ if (IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Suspend(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = true;
+ }
+ else
+ {
+ if (!IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Resume(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = false;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ public void PinThread(KThread thread)
+ {
+ if (!thread.TerminationRequested)
+ {
+ PinnedThreads[thread.CurrentCore] = thread;
+
+ thread.Pin();
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+ }
+
+ public void UnpinThread(KThread thread)
+ {
+ if (!thread.TerminationRequested)
+ {
+ thread.Unpin();
+
+ PinnedThreads[thread.CurrentCore] = null;
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+ }
+
+ public bool IsExceptionUserThread(KThread thread)
+ {
+ // TODO
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
new file mode 100644
index 00000000..c99e3112
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
@@ -0,0 +1,328 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KProcessCapabilities
+ {
+ public byte[] SvcAccessMask { get; }
+ public byte[] IrqAccessMask { get; }
+
+ public ulong AllowedCpuCoresMask { get; private set; }
+ public ulong AllowedThreadPriosMask { get; private set; }
+
+ public uint DebuggingFlags { get; private set; }
+ public uint HandleTableSize { get; private set; }
+ public uint KernelReleaseVersion { get; private set; }
+ public uint ApplicationType { get; private set; }
+
+ public KProcessCapabilities()
+ {
+ // length / number of bits of the underlying type
+ SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
+ IrqAccessMask = new byte[0x80];
+ }
+
+ public Result InitializeForKernel(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ AllowedCpuCoresMask = 0xf;
+ AllowedThreadPriosMask = ulong.MaxValue;
+ DebuggingFlags &= ~3u;
+ KernelReleaseVersion = KProcess.KernelVersionPacked;
+
+ return Parse(capabilities, memoryManager);
+ }
+
+ public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ return Parse(capabilities, memoryManager);
+ }
+
+ private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
+ {
+ int mask0 = 0;
+ int mask1 = 0;
+
+ for (int index = 0; index < capabilities.Length; index++)
+ {
+ uint cap = capabilities[index];
+
+ if (cap.GetCapabilityType() != CapabilityType.MapRange)
+ {
+ Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ else
+ {
+ if ((uint)index + 1 >= capabilities.Length)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ uint prevCap = cap;
+
+ cap = capabilities[++index];
+
+ if (((cap + 1) & ~cap) != 0x40)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if ((cap & 0x78000000) != 0)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ if ((cap & 0x7ffff80) == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ long address = ((long)prevCap << 5) & 0xffffff000;
+ long size = ((long)cap << 5) & 0xfffff000;
+
+ if (((ulong)(address + size - 1) >> 36) != 0)
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KMemoryPermission perm = (prevCap >> 31) != 0
+ ? KMemoryPermission.Read
+ : KMemoryPermission.ReadAndWrite;
+
+ Result result;
+
+ if ((cap >> 31) != 0)
+ {
+ result = memoryManager.MapNormalMemory(address, size, perm);
+ }
+ else
+ {
+ result = memoryManager.MapIoMemory(address, size, perm);
+ }
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+ }
+ }
+
+ return Result.Success;
+ }
+
+ private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
+ {
+ CapabilityType code = cap.GetCapabilityType();
+
+ if (code == CapabilityType.Invalid)
+ {
+ return KernelResult.InvalidCapability;
+ }
+ else if (code == CapabilityType.Padding)
+ {
+ return Result.Success;
+ }
+
+ int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
+
+ // Check if the property was already set.
+ if (((mask0 & codeMask) & 0x1e008) != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ mask0 |= codeMask;
+
+ switch (code)
+ {
+ case CapabilityType.CorePriority:
+ {
+ if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
+ {
+ return KernelResult.InvalidCapability;
+ }
+
+ uint lowestCpuCore = (cap >> 16) & 0xff;
+ uint highestCpuCore = (cap >> 24) & 0xff;
+
+ if (lowestCpuCore > highestCpuCore)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ uint highestThreadPrio = (cap >> 4) & 0x3f;
+ uint lowestThreadPrio = (cap >> 10) & 0x3f;
+
+ if (lowestThreadPrio > highestThreadPrio)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if (highestCpuCore >= KScheduler.CpuCoresCount)
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+
+ AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
+ AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
+
+ break;
+ }
+
+ case CapabilityType.SyscallMask:
+ {
+ int slot = ((int)cap >> 29) & 7;
+
+ int svcSlotMask = 1 << slot;
+
+ if ((mask1 & svcSlotMask) != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ mask1 |= svcSlotMask;
+
+ uint svcMask = (cap >> 5) & 0xffffff;
+
+ int baseSvc = slot * 24;
+
+ for (int index = 0; index < 24; index++)
+ {
+ if (((svcMask >> index) & 1) == 0)
+ {
+ continue;
+ }
+
+ int svcId = baseSvc + index;
+
+ if (svcId >= KernelConstants.SupervisorCallCount)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7));
+ }
+
+ break;
+ }
+
+ case CapabilityType.MapIoPage:
+ {
+ long address = ((long)cap << 4) & 0xffffff000;
+
+ memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
+
+ break;
+ }
+
+ case CapabilityType.MapRegion:
+ {
+ // TODO: Implement capabilities for MapRegion
+
+ break;
+ }
+
+ case CapabilityType.InterruptPair:
+ {
+ // TODO: GIC distributor check.
+ int irq0 = ((int)cap >> 12) & 0x3ff;
+ int irq1 = ((int)cap >> 22) & 0x3ff;
+
+ if (irq0 != 0x3ff)
+ {
+ IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7));
+ }
+
+ if (irq1 != 0x3ff)
+ {
+ IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7));
+ }
+
+ break;
+ }
+
+ case CapabilityType.ProgramType:
+ {
+ uint applicationType = (cap >> 14);
+
+ if (applicationType > 7)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ ApplicationType = applicationType;
+
+ break;
+ }
+
+ case CapabilityType.KernelVersion:
+ {
+ // Note: This check is bugged on kernel too, we are just replicating the bug here.
+ if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ KernelReleaseVersion = cap;
+
+ break;
+ }
+
+ case CapabilityType.HandleTable:
+ {
+ uint handleTableSize = cap >> 26;
+
+ if (handleTableSize > 0x3ff)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ HandleTableSize = handleTableSize;
+
+ break;
+ }
+
+ case CapabilityType.DebugFlags:
+ {
+ uint debuggingFlags = cap >> 19;
+
+ if (debuggingFlags > 3)
+ {
+ return KernelResult.ReservedValue;
+ }
+
+ DebuggingFlags &= ~3u;
+ DebuggingFlags |= debuggingFlags;
+
+ break;
+ }
+
+ default: return KernelResult.InvalidCapability;
+ }
+
+ return Result.Success;
+ }
+
+ private static ulong GetMaskFromMinMax(uint min, uint max)
+ {
+ uint range = max - min + 1;
+
+ if (range == 64)
+ {
+ return ulong.MaxValue;
+ }
+
+ ulong mask = (1UL << (int)range) - 1;
+
+ return mask << (int)min;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs
new file mode 100644
index 00000000..f55e3c10
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs
@@ -0,0 +1,77 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KTlsPageInfo
+ {
+ public const int TlsEntrySize = 0x200;
+
+ public ulong PageVirtualAddress { get; }
+ public ulong PagePhysicalAddress { get; }
+
+ private readonly bool[] _isSlotFree;
+
+ public KTlsPageInfo(ulong pageVirtualAddress, ulong pagePhysicalAddress)
+ {
+ PageVirtualAddress = pageVirtualAddress;
+ PagePhysicalAddress = pagePhysicalAddress;
+
+ _isSlotFree = new bool[KPageTableBase.PageSize / TlsEntrySize];
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ _isSlotFree[index] = true;
+ }
+ }
+
+ public bool TryGetFreePage(out ulong address)
+ {
+ address = PageVirtualAddress;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ if (_isSlotFree[index])
+ {
+ _isSlotFree[index] = false;
+
+ return true;
+ }
+
+ address += TlsEntrySize;
+ }
+
+ address = 0;
+
+ return false;
+ }
+
+ public bool IsFull()
+ {
+ bool hasFree = false;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ hasFree |= _isSlotFree[index];
+ }
+
+ return !hasFree;
+ }
+
+ public bool IsEmpty()
+ {
+ bool allFree = true;
+
+ for (int index = 0; index < _isSlotFree.Length; index++)
+ {
+ allFree &= _isSlotFree[index];
+ }
+
+ return allFree;
+ }
+
+ public void FreeTlsSlot(ulong address)
+ {
+ _isSlotFree[(address - PageVirtualAddress) / TlsEntrySize] = true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs
new file mode 100644
index 00000000..0fde495c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs
@@ -0,0 +1,61 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class KTlsPageManager
+ {
+ private const int TlsEntrySize = 0x200;
+
+ private long _pagePosition;
+
+ private int _usedSlots;
+
+ private bool[] _slots;
+
+ public bool IsEmpty => _usedSlots == 0;
+ public bool IsFull => _usedSlots == _slots.Length;
+
+ public KTlsPageManager(long pagePosition)
+ {
+ _pagePosition = pagePosition;
+
+ _slots = new bool[KPageTableBase.PageSize / TlsEntrySize];
+ }
+
+ public bool TryGetFreeTlsAddr(out long position)
+ {
+ position = _pagePosition;
+
+ for (int index = 0; index < _slots.Length; index++)
+ {
+ if (!_slots[index])
+ {
+ _slots[index] = true;
+
+ _usedSlots++;
+
+ return true;
+ }
+
+ position += TlsEntrySize;
+ }
+
+ position = 0;
+
+ return false;
+ }
+
+ public void FreeTlsSlot(int slot)
+ {
+ if ((uint)slot > _slots.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(slot));
+ }
+
+ _slots[slot] = false;
+
+ _usedSlots--;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
new file mode 100644
index 00000000..87296830
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Cpu;
+using Ryujinx.Memory;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessContext : IProcessContext
+ {
+ public IVirtualMemoryManager AddressSpace { get; }
+
+ public ProcessContext(IVirtualMemoryManager asManager)
+ {
+ AddressSpace = asManager;
+ }
+
+ public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
+ {
+ return new ProcessExecutionContext();
+ }
+
+ public void Execute(IExecutionContext context, ulong codeAddress)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void InvalidateCacheRegion(ulong address, ulong size)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs
new file mode 100644
index 00000000..1c5798b4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessContextFactory : IProcessContextFactory
+ {
+ public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
+ {
+ return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs
new file mode 100644
index 00000000..a79978ac
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ [Flags]
+ enum ProcessCreationFlags
+ {
+ Is64Bit = 1 << 0,
+
+ AddressSpaceShift = 1,
+ AddressSpace32Bit = 0 << AddressSpaceShift,
+ AddressSpace64BitDeprecated = 1 << AddressSpaceShift,
+ AddressSpace32BitWithoutAlias = 2 << AddressSpaceShift,
+ AddressSpace64Bit = 3 << AddressSpaceShift,
+ AddressSpaceMask = 7 << AddressSpaceShift,
+
+ EnableDebug = 1 << 4,
+ EnableAslr = 1 << 5,
+ IsApplication = 1 << 6,
+ DeprecatedUseSecureMemory = 1 << 7,
+
+ PoolPartitionShift = 7,
+ PoolPartitionApplication = 0 << PoolPartitionShift,
+ PoolPartitionApplet = 1 << PoolPartitionShift,
+ PoolPartitionSystem = 2 << PoolPartitionShift,
+ PoolPartitionSystemNonSecure = 3 << PoolPartitionShift,
+ PoolPartitionMask = 0xf << PoolPartitionShift,
+
+ OptimizeMemoryAllocation = 1 << 11,
+
+ All =
+ Is64Bit |
+ AddressSpaceMask |
+ EnableDebug |
+ EnableAslr |
+ IsApplication |
+ DeprecatedUseSecureMemory |
+ PoolPartitionMask |
+ OptimizeMemoryAllocation
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs
new file mode 100644
index 00000000..c05bb574
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ readonly struct ProcessCreationInfo
+ {
+ public string Name { get; }
+
+ public int Version { get; }
+ public ulong TitleId { get; }
+
+ public ulong CodeAddress { get; }
+ public int CodePagesCount { get; }
+
+ public ProcessCreationFlags Flags { get; }
+ public int ResourceLimitHandle { get; }
+ public int SystemResourcePagesCount { get; }
+
+ public ProcessCreationInfo(
+ string name,
+ int version,
+ ulong titleId,
+ ulong codeAddress,
+ int codePagesCount,
+ ProcessCreationFlags flags,
+ int resourceLimitHandle,
+ int systemResourcePagesCount)
+ {
+ Name = name;
+ Version = version;
+ TitleId = titleId;
+ CodeAddress = codeAddress;
+ CodePagesCount = codePagesCount;
+ Flags = flags;
+ ResourceLimitHandle = resourceLimitHandle;
+ SystemResourcePagesCount = systemResourcePagesCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
new file mode 100644
index 00000000..77fcdf33
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs
@@ -0,0 +1,46 @@
+using ARMeilleure.State;
+using Ryujinx.Cpu;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessExecutionContext : IExecutionContext
+ {
+ public ulong Pc => 0UL;
+
+ public ulong CntfrqEl0 { get; set; }
+ public ulong CntpctEl0 => 0UL;
+
+ public long TpidrEl0 { get; set; }
+ public long TpidrroEl0 { get; set; }
+
+ public uint Pstate { get; set; }
+
+ public uint Fpcr { get; set; }
+ public uint Fpsr { get; set; }
+
+ public bool IsAarch32 { get => false; set { } }
+
+ public bool Running { get; private set; } = true;
+
+ private readonly ulong[] _x = new ulong[32];
+
+ public ulong GetX(int index) => _x[index];
+ public void SetX(int index, ulong value) => _x[index] = value;
+
+ public V128 GetV(int index) => default;
+ public void SetV(int index, V128 value) { }
+
+ public void RequestInterrupt()
+ {
+ }
+
+ public void StopRunning()
+ {
+ Running = false;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs
new file mode 100644
index 00000000..5ef3077e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ enum ProcessState : byte
+ {
+ Created = 0,
+ CreatedAttached = 1,
+ Started = 2,
+ Crashed = 3,
+ Attached = 4,
+ Exiting = 5,
+ Exited = 6,
+ DebugSuspended = 7
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
new file mode 100644
index 00000000..4cf67172
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Process
+{
+ class ProcessTamperInfo
+ {
+ public KProcess Process { get; }
+ public IEnumerable<string> BuildIds { get; }
+ public IEnumerable<ulong> CodeAddresses { get; }
+ public ulong HeapAddress { get; }
+ public ulong AliasAddress { get; }
+ public ulong AslrAddress { get; }
+
+ public ProcessTamperInfo(KProcess process, IEnumerable<string> buildIds, IEnumerable<ulong> codeAddresses, ulong heapAddress, ulong aliasAddress, ulong aslrAddress)
+ {
+ Process = process;
+ BuildIds = buildIds;
+ CodeAddresses = codeAddresses;
+ HeapAddress = heapAddress;
+ AliasAddress = aliasAddress;
+ AslrAddress = aslrAddress;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs
new file mode 100644
index 00000000..511ee99a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ enum CodeMemoryOperation : uint
+ {
+ Map,
+ MapToOwner,
+ Unmap,
+ UnmapFromOwner
+ };
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
new file mode 100644
index 00000000..3cf7ba74
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs
@@ -0,0 +1,34 @@
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ enum InfoType : uint
+ {
+ CoreMask,
+ PriorityMask,
+ AliasRegionAddress,
+ AliasRegionSize,
+ HeapRegionAddress,
+ HeapRegionSize,
+ TotalMemorySize,
+ UsedMemorySize,
+ DebuggerAttached,
+ ResourceLimit,
+ IdleTickCount,
+ RandomEntropy,
+ AslrRegionAddress,
+ AslrRegionSize,
+ StackRegionAddress,
+ StackRegionSize,
+ SystemResourceSizeTotal,
+ SystemResourceSizeUsed,
+ ProgramId,
+ // NOTE: Added in 4.0.0, removed in 5.0.0.
+ InitialProcessIdRange,
+ UserExceptionContextAddress,
+ TotalNonSystemMemorySize,
+ UsedNonSystemMemorySize,
+ IsApplication,
+ FreeThreadCount,
+ ThreadTickCount,
+ MesosphereCurrentProcess = 65001
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs
new file mode 100644
index 00000000..a71cce1f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs
@@ -0,0 +1,37 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ struct MemoryInfo
+ {
+ public ulong Address;
+ public ulong Size;
+ public MemoryState State;
+ public MemoryAttribute Attribute;
+ public KMemoryPermission Permission;
+ public int IpcRefCount;
+ public int DeviceRefCount;
+#pragma warning disable CS0414
+ private int _padding;
+#pragma warning restore CS0414
+
+ public MemoryInfo(
+ ulong address,
+ ulong size,
+ MemoryState state,
+ MemoryAttribute attribute,
+ KMemoryPermission permission,
+ int ipcRefCount,
+ int deviceRefCount)
+ {
+ Address = address;
+ Size = size;
+ State = state;
+ Attribute = attribute;
+ Permission = permission;
+ IpcRefCount = ipcRefCount;
+ DeviceRefCount = deviceRefCount;
+ _padding = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs
new file mode 100644
index 00000000..154164fb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+ class PointerSizedAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs
new file mode 100644
index 00000000..b8839d1d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ class SvcAttribute : Attribute
+ {
+ public int Id { get; }
+
+ public SvcAttribute(int id)
+ {
+ Id = id;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs
new file mode 100644
index 00000000..a32d851f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ class SvcImplAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
new file mode 100644
index 00000000..3163c348
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
@@ -0,0 +1,3010 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Buffers;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ [SvcImpl]
+ class Syscall : ISyscallApi
+ {
+ private readonly KernelContext _context;
+
+ public Syscall(KernelContext context)
+ {
+ _context = context;
+ }
+
+ // Process
+
+ [Svc(0x24)]
+ public Result GetProcessId(out ulong pid, int handle)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KProcess process = currentProcess.HandleTable.GetKProcess(handle);
+
+ if (process == null)
+ {
+ KThread thread = currentProcess.HandleTable.GetKThread(handle);
+
+ if (thread != null)
+ {
+ process = thread.Owner;
+ }
+
+ // TODO: KDebugEvent.
+ }
+
+ pid = process?.Pid ?? 0;
+
+ return process != null
+ ? Result.Success
+ : KernelResult.InvalidHandle;
+ }
+
+ public Result CreateProcess(
+ out int handle,
+ ProcessCreationInfo info,
+ ReadOnlySpan<uint> capabilities,
+ IProcessContextFactory contextFactory,
+ ThreadStart customThreadStart = null)
+ {
+ handle = 0;
+
+ if ((info.Flags & ~ProcessCreationFlags.All) != 0)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ // TODO: Address space check.
+
+ if ((info.Flags & ProcessCreationFlags.PoolPartitionMask) > ProcessCreationFlags.PoolPartitionSystemNonSecure)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ if ((info.CodeAddress & 0x1fffff) != 0)
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (info.CodePagesCount < 0 || info.SystemResourcePagesCount < 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (info.Flags.HasFlag(ProcessCreationFlags.OptimizeMemoryAllocation) &&
+ !info.Flags.HasFlag(ProcessCreationFlags.IsApplication))
+ {
+ return KernelResult.InvalidThread;
+ }
+
+ KHandleTable handleTable = KernelStatic.GetCurrentProcess().HandleTable;
+
+ KProcess process = new KProcess(_context);
+
+ using var _ = new OnScopeExit(process.DecrementReferenceCount);
+
+ KResourceLimit resourceLimit;
+
+ if (info.ResourceLimitHandle != 0)
+ {
+ resourceLimit = handleTable.GetObject<KResourceLimit>(info.ResourceLimitHandle);
+
+ if (resourceLimit == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+ }
+ else
+ {
+ resourceLimit = _context.ResourceLimit;
+ }
+
+ MemoryRegion memRegion = (info.Flags & ProcessCreationFlags.PoolPartitionMask) switch
+ {
+ ProcessCreationFlags.PoolPartitionApplication => MemoryRegion.Application,
+ ProcessCreationFlags.PoolPartitionApplet => MemoryRegion.Applet,
+ ProcessCreationFlags.PoolPartitionSystem => MemoryRegion.Service,
+ ProcessCreationFlags.PoolPartitionSystemNonSecure => MemoryRegion.NvServices,
+ _ => MemoryRegion.NvServices
+ };
+
+ Result result = process.Initialize(
+ info,
+ capabilities,
+ resourceLimit,
+ memRegion,
+ contextFactory,
+ customThreadStart);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ _context.Processes.TryAdd(process.Pid, process);
+
+ return handleTable.GenerateHandle(process, out handle);
+ }
+
+ public Result StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KProcess>(handle);
+
+ if (process == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if ((uint)cpuCore >= KScheduler.CpuCoresCount || !process.IsCpuCoreAllowed(cpuCore))
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+
+ if ((uint)priority >= KScheduler.PrioritiesCount || !process.IsPriorityAllowed(priority))
+ {
+ return KernelResult.InvalidPriority;
+ }
+
+ process.DefaultCpuCore = cpuCore;
+
+ Result result = process.Start(priority, mainThreadStackSize);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ process.IncrementReferenceCount();
+
+ return Result.Success;
+ }
+
+ [Svc(0x5f)]
+ public Result FlushProcessDataCache(int processHandle, ulong address, ulong size)
+ {
+ // FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0.
+ // As we don't support (and don't actually need) to flush the cache, this is stubbed.
+ return Result.Success;
+ }
+
+ // IPC
+
+ [Svc(0x1f)]
+ public Result ConnectToNamedPort(out int handle, [PointerSized] ulong namePtr)
+ {
+ handle = 0;
+
+ if (!KernelTransfer.UserToKernelString(out string name, namePtr, 12))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ return ConnectToNamedPort(out handle, name);
+ }
+
+ public Result ConnectToNamedPort(out int handle, string name)
+ {
+ handle = 0;
+
+ if (name.Length > 11)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KAutoObject autoObj = KAutoObject.FindNamedObject(_context, name);
+
+ if (autoObj is not KClientPort clientPort)
+ {
+ return KernelResult.NotFound;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Result result = currentProcess.HandleTable.ReserveHandle(out handle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = clientPort.Connect(out KClientSession clientSession);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CancelHandleReservation(handle);
+
+ return result;
+ }
+
+ currentProcess.HandleTable.SetReservedHandleObj(handle, clientSession);
+
+ clientSession.DecrementReferenceCount();
+
+ return result;
+ }
+
+ [Svc(0x21)]
+ public Result SendSyncRequest(int handle)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
+
+ if (session == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ return session.SendSyncRequest();
+ }
+
+ [Svc(0x22)]
+ public Result SendSyncRequestWithUserBuffer(
+ [PointerSized] ulong messagePtr,
+ [PointerSized] ulong messageSize,
+ int handle)
+ {
+ if (!PageAligned(messagePtr))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(messageSize) || messageSize == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (messagePtr + messageSize <= messagePtr)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
+
+ if (session == null)
+ {
+ result = KernelResult.InvalidHandle;
+ }
+ else
+ {
+ result = session.SendSyncRequest(messagePtr, messageSize);
+ }
+
+ Result result2 = currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+
+ if (result == Result.Success)
+ {
+ result = result2;
+ }
+
+ return result;
+ }
+
+ [Svc(0x23)]
+ public Result SendAsyncRequestWithUserBuffer(
+ out int doneEventHandle,
+ [PointerSized] ulong messagePtr,
+ [PointerSized] ulong messageSize,
+ int handle)
+ {
+ doneEventHandle = 0;
+
+ if (!PageAligned(messagePtr))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(messageSize) || messageSize == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (messagePtr + messageSize <= messagePtr)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ KResourceLimit resourceLimit = currentProcess.ResourceLimit;
+
+ if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Event, 1))
+ {
+ currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+
+ return KernelResult.ResLimitExceeded;
+ }
+
+ KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
+
+ if (session == null)
+ {
+ result = KernelResult.InvalidHandle;
+ }
+ else
+ {
+ KEvent doneEvent = new KEvent(_context);
+
+ result = currentProcess.HandleTable.GenerateHandle(doneEvent.ReadableEvent, out doneEventHandle);
+
+ if (result == Result.Success)
+ {
+ result = session.SendAsyncRequest(doneEvent.WritableEvent, messagePtr, messageSize);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CloseHandle(doneEventHandle);
+ }
+ }
+ }
+
+ if (result != Result.Success)
+ {
+ resourceLimit?.Release(LimitableResource.Event, 1);
+
+ currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+ }
+
+ return result;
+ }
+
+ [Svc(0x40)]
+ public Result CreateSession(
+ out int serverSessionHandle,
+ out int clientSessionHandle,
+ bool isLight,
+ [PointerSized] ulong namePtr)
+ {
+ return CreateSession(out serverSessionHandle, out clientSessionHandle, isLight, null);
+ }
+
+ public Result CreateSession(
+ out int serverSessionHandle,
+ out int clientSessionHandle,
+ bool isLight,
+ string name)
+ {
+ serverSessionHandle = 0;
+ clientSessionHandle = 0;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KResourceLimit resourceLimit = currentProcess.ResourceLimit;
+
+ if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Session, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ Result result;
+
+ if (isLight)
+ {
+ KLightSession session = new KLightSession(_context);
+
+ result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle);
+
+ if (result == Result.Success)
+ {
+ result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CloseHandle(serverSessionHandle);
+
+ serverSessionHandle = 0;
+ }
+ }
+
+ session.ServerSession.DecrementReferenceCount();
+ session.ClientSession.DecrementReferenceCount();
+ }
+ else
+ {
+ KSession session = new KSession(_context);
+
+ result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle);
+
+ if (result == Result.Success)
+ {
+ result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CloseHandle(serverSessionHandle);
+
+ serverSessionHandle = 0;
+ }
+ }
+
+ session.ServerSession.DecrementReferenceCount();
+ session.ClientSession.DecrementReferenceCount();
+ }
+
+ return result;
+ }
+
+ [Svc(0x41)]
+ public Result AcceptSession(out int sessionHandle, int portHandle)
+ {
+ sessionHandle = 0;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KServerPort serverPort = currentProcess.HandleTable.GetObject<KServerPort>(portHandle);
+
+ if (serverPort == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ Result result = currentProcess.HandleTable.ReserveHandle(out int handle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ KAutoObject session;
+
+ if (serverPort.IsLight)
+ {
+ session = serverPort.AcceptIncomingLightConnection();
+ }
+ else
+ {
+ session = serverPort.AcceptIncomingConnection();
+ }
+
+ if (session != null)
+ {
+ currentProcess.HandleTable.SetReservedHandleObj(handle, session);
+
+ session.DecrementReferenceCount();
+
+ sessionHandle = handle;
+
+ result = Result.Success;
+ }
+ else
+ {
+ currentProcess.HandleTable.CancelHandleReservation(handle);
+
+ result = KernelResult.NotFound;
+ }
+
+ return result;
+ }
+
+ [Svc(0x43)]
+ public Result ReplyAndReceive(
+ out int handleIndex,
+ [PointerSized] ulong handlesPtr,
+ int handlesCount,
+ int replyTargetHandle,
+ long timeout)
+ {
+ handleIndex = 0;
+
+ if ((uint)handlesCount > 0x40)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ ulong copySize = (ulong)((long)handlesCount * 4);
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ if (handlesPtr + copySize < handlesPtr)
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ int[] handles = new int[handlesCount];
+
+ if (!KernelTransfer.UserToKernelArray<int>(handlesPtr, handles))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ return ReplyAndReceive(out handleIndex, handles, replyTargetHandle, timeout);
+ }
+
+ public Result ReplyAndReceive(out int handleIndex, ReadOnlySpan<int> handles, int replyTargetHandle, long timeout)
+ {
+ handleIndex = 0;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KSynchronizationObject[] syncObjsArray = ArrayPool<KSynchronizationObject>.Shared.Rent(handles.Length);
+
+ Span<KSynchronizationObject> syncObjs = syncObjsArray.AsSpan(0, handles.Length);
+
+ for (int index = 0; index < handles.Length; index++)
+ {
+ KSynchronizationObject obj = currentProcess.HandleTable.GetObject<KSynchronizationObject>(handles[index]);
+
+ if (obj == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ syncObjs[index] = obj;
+ }
+
+ Result result = Result.Success;
+
+ if (replyTargetHandle != 0)
+ {
+ KServerSession replyTarget = currentProcess.HandleTable.GetObject<KServerSession>(replyTargetHandle);
+
+ if (replyTarget == null)
+ {
+ result = KernelResult.InvalidHandle;
+ }
+ else
+ {
+ result = replyTarget.Reply();
+ }
+ }
+
+ if (result == Result.Success)
+ {
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == Result.Success)
+ {
+ KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
+
+ if (session == null)
+ {
+ break;
+ }
+
+ if ((result = session.Receive()) != KernelResult.NotFound)
+ {
+ break;
+ }
+ }
+ }
+
+ ArrayPool<KSynchronizationObject>.Shared.Return(syncObjsArray);
+
+ return result;
+ }
+
+ [Svc(0x44)]
+ public Result ReplyAndReceiveWithUserBuffer(
+ out int handleIndex,
+ [PointerSized] ulong messagePtr,
+ [PointerSized] ulong messageSize,
+ [PointerSized] ulong handlesPtr,
+ int handlesCount,
+ int replyTargetHandle,
+ long timeout)
+ {
+ handleIndex = 0;
+
+ if ((uint)handlesCount > 0x40)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ ulong copySize = (ulong)((long)handlesCount * 4);
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ if (handlesPtr + copySize < handlesPtr)
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ int[] handles = new int[handlesCount];
+
+ if (!KernelTransfer.UserToKernelArray<int>(handlesPtr, handles))
+ {
+ currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+
+ return KernelResult.UserCopyFailed;
+ }
+
+ KSynchronizationObject[] syncObjs = new KSynchronizationObject[handlesCount];
+
+ for (int index = 0; index < handlesCount; index++)
+ {
+ KSynchronizationObject obj = currentProcess.HandleTable.GetObject<KSynchronizationObject>(handles[index]);
+
+ if (obj == null)
+ {
+ currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+
+ return KernelResult.InvalidHandle;
+ }
+
+ syncObjs[index] = obj;
+ }
+
+ if (replyTargetHandle != 0)
+ {
+ KServerSession replyTarget = currentProcess.HandleTable.GetObject<KServerSession>(replyTargetHandle);
+
+ if (replyTarget == null)
+ {
+ result = KernelResult.InvalidHandle;
+ }
+ else
+ {
+ result = replyTarget.Reply(messagePtr, messageSize);
+ }
+ }
+
+ if (result == Result.Success)
+ {
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == Result.Success)
+ {
+ KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
+
+ if (session == null)
+ {
+ break;
+ }
+
+ if ((result = session.Receive(messagePtr, messageSize)) != KernelResult.NotFound)
+ {
+ break;
+ }
+ }
+ }
+
+ currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize);
+
+ return result;
+ }
+
+ [Svc(0x70)]
+ public Result CreatePort(
+ out int serverPortHandle,
+ out int clientPortHandle,
+ int maxSessions,
+ bool isLight,
+ [PointerSized] ulong namePtr)
+ {
+ // The kernel doesn't use the name pointer, so we can just pass null as the name.
+ return CreatePort(out serverPortHandle, out clientPortHandle, maxSessions, isLight, null);
+ }
+
+ public Result CreatePort(
+ out int serverPortHandle,
+ out int clientPortHandle,
+ int maxSessions,
+ bool isLight,
+ string name)
+ {
+ serverPortHandle = clientPortHandle = 0;
+
+ if (maxSessions < 1)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KPort port = new KPort(_context, maxSessions, isLight, name);
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Result result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CloseHandle(clientPortHandle);
+ }
+
+ return result;
+ }
+
+ [Svc(0x71)]
+ public Result ManageNamedPort(out int handle, [PointerSized] ulong namePtr, int maxSessions)
+ {
+ handle = 0;
+
+ if (!KernelTransfer.UserToKernelString(out string name, namePtr, 12))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ if (name.Length > 11)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ return ManageNamedPort(out handle, name, maxSessions);
+ }
+
+ public Result ManageNamedPort(out int handle, string name, int maxSessions)
+ {
+ handle = 0;
+
+ if (maxSessions < 0)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ if (maxSessions == 0)
+ {
+ return KAutoObject.RemoveName(_context, name);
+ }
+
+ KPort port = new KPort(_context, maxSessions, false, null);
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ Result result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ result = port.ClientPort.SetName(name);
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CloseHandle(handle);
+ }
+
+ return result;
+ }
+
+ [Svc(0x72)]
+ public Result ConnectToPort(out int clientSessionHandle, int clientPortHandle)
+ {
+ clientSessionHandle = 0;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KClientPort clientPort = currentProcess.HandleTable.GetObject<KClientPort>(clientPortHandle);
+
+ if (clientPort == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ Result result = currentProcess.HandleTable.ReserveHandle(out int handle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ KAutoObject session;
+
+ if (clientPort.IsLight)
+ {
+ result = clientPort.ConnectLight(out KLightClientSession clientSession);
+
+ session = clientSession;
+ }
+ else
+ {
+ result = clientPort.Connect(out KClientSession clientSession);
+
+ session = clientSession;
+ }
+
+ if (result != Result.Success)
+ {
+ currentProcess.HandleTable.CancelHandleReservation(handle);
+
+ return result;
+ }
+
+ currentProcess.HandleTable.SetReservedHandleObj(handle, session);
+
+ session.DecrementReferenceCount();
+
+ clientSessionHandle = handle;
+
+ return result;
+ }
+
+ // Memory
+
+ [Svc(1)]
+ public Result SetHeapSize([PointerSized] out ulong address, [PointerSized] ulong size)
+ {
+ if ((size & 0xfffffffe001fffff) != 0)
+ {
+ address = 0;
+
+ return KernelResult.InvalidSize;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.MemoryManager.SetHeapSize(size, out address);
+ }
+
+ [Svc(2)]
+ public Result SetMemoryPermission([PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (permission != KMemoryPermission.None && (permission | KMemoryPermission.Write) != KMemoryPermission.ReadAndWrite)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission);
+ }
+
+ [Svc(3)]
+ public Result SetMemoryAttribute(
+ [PointerSized] ulong address,
+ [PointerSized] ulong size,
+ MemoryAttribute attributeMask,
+ MemoryAttribute attributeValue)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ MemoryAttribute attributes = attributeMask | attributeValue;
+
+ if (attributes != attributeMask ||
+ (attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ if (!process.MemoryManager.InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ Result result = process.MemoryManager.SetMemoryAttribute(
+ address,
+ size,
+ attributeMask,
+ attributeValue);
+
+ return result;
+ }
+
+ [Svc(4)]
+ public Result MapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
+ {
+ if (!PageAligned(src | dst))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (src + size <= src || dst + size <= dst)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(src, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(dst, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(dst, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.MemoryManager.Map(dst, src, size);
+ }
+
+ [Svc(5)]
+ public Result UnmapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size)
+ {
+ if (!PageAligned(src | dst))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (src + size <= src || dst + size <= dst)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(src, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(dst, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(dst, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.MemoryManager.Unmap(dst, src, size);
+ }
+
+ [Svc(6)]
+ public Result QueryMemory([PointerSized] ulong infoPtr, [PointerSized] out ulong pageInfo, [PointerSized] ulong address)
+ {
+ Result result = QueryMemory(out MemoryInfo info, out pageInfo, address);
+
+ if (result == Result.Success)
+ {
+ return KernelTransfer.KernelToUser(infoPtr, info)
+ ? Result.Success
+ : KernelResult.InvalidMemState;
+ }
+
+ return result;
+ }
+
+ public Result QueryMemory(out MemoryInfo info, out ulong pageInfo, ulong address)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KMemoryInfo blockInfo = process.MemoryManager.QueryMemory(address);
+
+ info = new MemoryInfo(
+ blockInfo.Address,
+ blockInfo.Size,
+ blockInfo.State & MemoryState.UserMask,
+ blockInfo.Attribute,
+ blockInfo.Permission & KMemoryPermission.UserMask,
+ blockInfo.IpcRefCount,
+ blockInfo.DeviceRefCount);
+
+ pageInfo = 0;
+
+ return Result.Success;
+ }
+
+ [Svc(0x13)]
+ public Result MapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if ((permission | KMemoryPermission.Write) != KMemoryPermission.ReadAndWrite)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject<KSharedMemory>(handle);
+
+ if (sharedMemory == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (currentProcess.MemoryManager.IsInvalidRegion(address, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(address, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ return sharedMemory.MapIntoProcess(
+ currentProcess.MemoryManager,
+ address,
+ size,
+ currentProcess,
+ permission);
+ }
+
+ [Svc(0x14)]
+ public Result UnmapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject<KSharedMemory>(handle);
+
+ if (sharedMemory == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (currentProcess.MemoryManager.IsInvalidRegion(address, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(address, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ return sharedMemory.UnmapFromProcess(
+ currentProcess.MemoryManager,
+ address,
+ size,
+ currentProcess);
+ }
+
+ [Svc(0x15)]
+ public Result CreateTransferMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
+ {
+ handle = 0;
+
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (permission > KMemoryPermission.ReadAndWrite || permission == KMemoryPermission.Write)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KResourceLimit resourceLimit = process.ResourceLimit;
+
+ if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.TransferMemory, 1))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ void CleanUpForError()
+ {
+ resourceLimit?.Release(LimitableResource.TransferMemory, 1);
+ }
+
+ if (!process.MemoryManager.InsideAddrSpace(address, size))
+ {
+ CleanUpForError();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ KTransferMemory transferMemory = new KTransferMemory(_context);
+
+ Result result = transferMemory.Initialize(address, size, permission);
+
+ if (result != Result.Success)
+ {
+ CleanUpForError();
+
+ return result;
+ }
+
+ result = process.HandleTable.GenerateHandle(transferMemory, out handle);
+
+ transferMemory.DecrementReferenceCount();
+
+ return result;
+ }
+
+ [Svc(0x51)]
+ public Result MapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (permission > KMemoryPermission.ReadAndWrite || permission == KMemoryPermission.Write)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KTransferMemory transferMemory = currentProcess.HandleTable.GetObject<KTransferMemory>(handle);
+
+ if (transferMemory == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (currentProcess.MemoryManager.IsInvalidRegion(address, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(address, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ return transferMemory.MapIntoProcess(
+ currentProcess.MemoryManager,
+ address,
+ size,
+ currentProcess,
+ permission);
+ }
+
+ [Svc(0x52)]
+ public Result UnmapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KTransferMemory transferMemory = currentProcess.HandleTable.GetObject<KTransferMemory>(handle);
+
+ if (transferMemory == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (currentProcess.MemoryManager.IsInvalidRegion(address, size) ||
+ currentProcess.MemoryManager.InsideHeapRegion(address, size) ||
+ currentProcess.MemoryManager.InsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ return transferMemory.UnmapFromProcess(
+ currentProcess.MemoryManager,
+ address,
+ size,
+ currentProcess);
+ }
+
+ [Svc(0x2c)]
+ public Result MapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(address, size) ||
+ currentProcess.MemoryManager.OutsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.MemoryManager.MapPhysicalMemory(address, size);
+ }
+
+ [Svc(0x2d)]
+ public Result UnmapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size)
+ {
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (address + size <= address)
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0)
+ {
+ return KernelResult.InvalidState;
+ }
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(address, size) ||
+ currentProcess.MemoryManager.OutsideAliasRegion(address, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.MemoryManager.UnmapPhysicalMemory(address, size);
+ }
+
+ [Svc(0x4b)]
+ public Result CreateCodeMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size)
+ {
+ handle = 0;
+
+ if (!PageAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (size + address <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ KCodeMemory codeMemory = new KCodeMemory(_context);
+
+ using var _ = new OnScopeExit(codeMemory.DecrementReferenceCount);
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.MemoryManager.InsideAddrSpace(address, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ Result result = codeMemory.Initialize(address, size);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle);
+ }
+
+ [Svc(0x4c)]
+ public Result ControlCodeMemory(
+ int handle,
+ CodeMemoryOperation op,
+ ulong address,
+ ulong size,
+ KMemoryPermission permission)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KCodeMemory codeMemory = currentProcess.HandleTable.GetObject<KCodeMemory>(handle);
+
+ // Newer versions of the kernel also returns an error here if the owner and process
+ // where the operation will happen are the same. We do not return an error here
+ // for homebrew because some of them requires this to be patched out to work (for JIT).
+ if (codeMemory == null || (!currentProcess.AllowCodeMemoryForJit && codeMemory.Owner == currentProcess))
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ switch (op)
+ {
+ case CodeMemoryOperation.Map:
+ if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (permission != KMemoryPermission.ReadAndWrite)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ return codeMemory.Map(address, size, permission);
+
+ case CodeMemoryOperation.MapToOwner:
+ if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (permission != KMemoryPermission.Read && permission != KMemoryPermission.ReadAndExecute)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ return codeMemory.MapToOwner(address, size, permission);
+
+ case CodeMemoryOperation.Unmap:
+ if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (permission != KMemoryPermission.None)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ return codeMemory.Unmap(address, size);
+
+ case CodeMemoryOperation.UnmapFromOwner:
+ if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (permission != KMemoryPermission.None)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ return codeMemory.UnmapFromOwner(address, size);
+
+ default: return KernelResult.InvalidEnumValue;
+ }
+ }
+
+ [Svc(0x73)]
+ public Result SetProcessMemoryPermission(
+ int handle,
+ [PointerSized] ulong src,
+ [PointerSized] ulong size,
+ KMemoryPermission permission)
+ {
+ if (!PageAligned(src))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (permission != KMemoryPermission.None &&
+ permission != KMemoryPermission.Read &&
+ permission != KMemoryPermission.ReadAndWrite &&
+ permission != KMemoryPermission.ReadAndExecute)
+ {
+ return KernelResult.InvalidPermission;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+ if (targetProcess == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
+ }
+
+ [Svc(0x74)]
+ public Result MapProcessMemory(
+ [PointerSized] ulong dst,
+ int handle,
+ ulong src,
+ [PointerSized] ulong size)
+ {
+ if (!PageAligned(src) || !PageAligned(dst))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (dst + size <= dst || src + size <= src)
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess dstProcess = KernelStatic.GetCurrentProcess();
+ KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
+
+ if (srcProcess == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
+ !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KPageList pageList = new KPageList();
+
+ Result result = srcProcess.MemoryManager.GetPagesIfStateEquals(
+ src,
+ size,
+ MemoryState.MapProcessAllowed,
+ MemoryState.MapProcessAllowed,
+ KMemoryPermission.None,
+ KMemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ pageList);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite);
+ }
+
+ [Svc(0x75)]
+ public Result UnmapProcessMemory(
+ [PointerSized] ulong dst,
+ int handle,
+ ulong src,
+ [PointerSized] ulong size)
+ {
+ if (!PageAligned(src) || !PageAligned(dst))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ if (dst + size <= dst || src + size <= src)
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ KProcess dstProcess = KernelStatic.GetCurrentProcess();
+ KProcess srcProcess = dstProcess.HandleTable.GetObject<KProcess>(handle);
+
+ if (srcProcess == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) ||
+ !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ Result result = dstProcess.MemoryManager.UnmapProcessMemory(dst, size, srcProcess.MemoryManager, src);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ return Result.Success;
+ }
+
+ [Svc(0x77)]
+ public Result MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
+ {
+ if (!PageAligned(dst) || !PageAligned(src))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+ if (targetProcess == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
+ targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
+ targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
+ targetProcess.MemoryManager.InsideHeapRegion(dst, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (size + dst <= dst || size + src <= src)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size);
+ }
+
+ [Svc(0x78)]
+ public Result UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
+ {
+ if (!PageAligned(dst) || !PageAligned(src))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ if (!PageAligned(size) || size == 0)
+ {
+ return KernelResult.InvalidSize;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+ if (targetProcess == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
+ targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
+ targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
+ targetProcess.MemoryManager.InsideHeapRegion(dst, size))
+ {
+ return KernelResult.InvalidMemRange;
+ }
+
+ if (size + dst <= dst || size + src <= src)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
+ }
+
+ private static bool PageAligned(ulong address)
+ {
+ return (address & (KPageTableBase.PageSize - 1)) == 0;
+ }
+
+ // System
+
+ [Svc(0x7b)]
+ public Result TerminateProcess(int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ process = process.HandleTable.GetObject<KProcess>(handle);
+
+ Result result;
+
+ if (process != null)
+ {
+ if (process == KernelStatic.GetCurrentProcess())
+ {
+ result = Result.Success;
+ process.DecrementToZeroWhileTerminatingCurrent();
+ }
+ else
+ {
+ result = process.Terminate();
+ process.DecrementReferenceCount();
+ }
+ }
+ else
+ {
+ result = KernelResult.InvalidHandle;
+ }
+
+ return result;
+ }
+
+ [Svc(7)]
+ public void ExitProcess()
+ {
+ KernelStatic.GetCurrentProcess().TerminateCurrentProcess();
+ }
+
+ [Svc(0x11)]
+ public Result SignalEvent(int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KWritableEvent writableEvent = process.HandleTable.GetObject<KWritableEvent>(handle);
+
+ Result result;
+
+ if (writableEvent != null)
+ {
+ writableEvent.Signal();
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidHandle;
+ }
+
+ return result;
+ }
+
+ [Svc(0x12)]
+ public Result ClearEvent(int handle)
+ {
+ Result result;
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KWritableEvent writableEvent = process.HandleTable.GetObject<KWritableEvent>(handle);
+
+ if (writableEvent == null)
+ {
+ KReadableEvent readableEvent = process.HandleTable.GetObject<KReadableEvent>(handle);
+
+ result = readableEvent?.Clear() ?? KernelResult.InvalidHandle;
+ }
+ else
+ {
+ result = writableEvent.Clear();
+ }
+
+ return result;
+ }
+
+ [Svc(0x16)]
+ public Result CloseHandle(int handle)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ return currentProcess.HandleTable.CloseHandle(handle) ? Result.Success : KernelResult.InvalidHandle;
+ }
+
+ [Svc(0x17)]
+ public Result ResetSignal(int handle)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KReadableEvent readableEvent = currentProcess.HandleTable.GetObject<KReadableEvent>(handle);
+
+ Result result;
+
+ if (readableEvent != null)
+ {
+ result = readableEvent.ClearIfSignaled();
+ }
+ else
+ {
+ KProcess process = currentProcess.HandleTable.GetKProcess(handle);
+
+ if (process != null)
+ {
+ result = process.ClearIfNotExited();
+ }
+ else
+ {
+ result = KernelResult.InvalidHandle;
+ }
+ }
+
+ return result;
+ }
+
+ [Svc(0x1e)]
+ public ulong GetSystemTick()
+ {
+ return _context.TickSource.Counter;
+ }
+
+ [Svc(0x26)]
+ public void Break(ulong reason)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if ((reason & (1UL << 31)) == 0)
+ {
+ currentThread.PrintGuestStackTrace();
+ currentThread.PrintGuestRegisterPrintout();
+
+ // As the process is exiting, this is probably caused by emulation termination.
+ if (currentThread.Owner.State == ProcessState.Exiting)
+ {
+ return;
+ }
+
+ // TODO: Debug events.
+ currentThread.Owner.TerminateCurrentProcess();
+
+ throw new GuestBrokeExecutionException();
+ }
+ else
+ {
+ Logger.Debug?.Print(LogClass.KernelSvc, "Debugger triggered.");
+ }
+ }
+
+ [Svc(0x27)]
+ public void OutputDebugString([PointerSized] ulong strPtr, [PointerSized] ulong size)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ string str = MemoryHelper.ReadAsciiString(process.CpuMemory, strPtr, (long)size);
+
+ Logger.Warning?.Print(LogClass.KernelSvc, str);
+ }
+
+ [Svc(0x29)]
+ public Result GetInfo(out ulong value, InfoType id, int handle, long subId)
+ {
+ value = 0;
+
+ switch (id)
+ {
+ case InfoType.CoreMask:
+ case InfoType.PriorityMask:
+ case InfoType.AliasRegionAddress:
+ case InfoType.AliasRegionSize:
+ case InfoType.HeapRegionAddress:
+ case InfoType.HeapRegionSize:
+ case InfoType.TotalMemorySize:
+ case InfoType.UsedMemorySize:
+ case InfoType.AslrRegionAddress:
+ case InfoType.AslrRegionSize:
+ case InfoType.StackRegionAddress:
+ case InfoType.StackRegionSize:
+ case InfoType.SystemResourceSizeTotal:
+ case InfoType.SystemResourceSizeUsed:
+ case InfoType.ProgramId:
+ case InfoType.UserExceptionContextAddress:
+ case InfoType.TotalNonSystemMemorySize:
+ case InfoType.UsedNonSystemMemorySize:
+ case InfoType.IsApplication:
+ case InfoType.FreeThreadCount:
+ {
+ if (subId != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ KProcess process = currentProcess.HandleTable.GetKProcess(handle);
+
+ if (process == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ switch (id)
+ {
+ case InfoType.CoreMask: value = process.Capabilities.AllowedCpuCoresMask; break;
+ case InfoType.PriorityMask: value = process.Capabilities.AllowedThreadPriosMask; break;
+
+ case InfoType.AliasRegionAddress: value = process.MemoryManager.AliasRegionStart; break;
+ case InfoType.AliasRegionSize:
+ value = (process.MemoryManager.AliasRegionEnd -
+ process.MemoryManager.AliasRegionStart); break;
+
+ case InfoType.HeapRegionAddress: value = process.MemoryManager.HeapRegionStart; break;
+ case InfoType.HeapRegionSize:
+ value = (process.MemoryManager.HeapRegionEnd -
+ process.MemoryManager.HeapRegionStart); break;
+
+ case InfoType.TotalMemorySize: value = process.GetMemoryCapacity(); break;
+
+ case InfoType.UsedMemorySize: value = process.GetMemoryUsage(); break;
+
+ case InfoType.AslrRegionAddress: value = process.MemoryManager.GetAddrSpaceBaseAddr(); break;
+
+ case InfoType.AslrRegionSize: value = process.MemoryManager.GetAddrSpaceSize(); break;
+
+ case InfoType.StackRegionAddress: value = process.MemoryManager.StackRegionStart; break;
+ case InfoType.StackRegionSize:
+ value = (process.MemoryManager.StackRegionEnd -
+ process.MemoryManager.StackRegionStart); break;
+
+ case InfoType.SystemResourceSizeTotal: value = process.PersonalMmHeapPagesCount * KPageTableBase.PageSize; break;
+
+ case InfoType.SystemResourceSizeUsed:
+ if (process.PersonalMmHeapPagesCount != 0)
+ {
+ value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize;
+ }
+
+ break;
+
+ case InfoType.ProgramId: value = process.TitleId; break;
+
+ case InfoType.UserExceptionContextAddress: value = process.UserExceptionContextAddress; break;
+
+ case InfoType.TotalNonSystemMemorySize: value = process.GetMemoryCapacityWithoutPersonalMmHeap(); break;
+
+ case InfoType.UsedNonSystemMemorySize: value = process.GetMemoryUsageWithoutPersonalMmHeap(); break;
+
+ case InfoType.IsApplication: value = process.IsApplication ? 1UL : 0UL; break;
+
+ case InfoType.FreeThreadCount:
+ if (process.ResourceLimit != null)
+ {
+ value = (ulong)(process.ResourceLimit.GetLimitValue(LimitableResource.Thread) -
+ process.ResourceLimit.GetCurrentValue(LimitableResource.Thread));
+ }
+ else
+ {
+ value = 0;
+ }
+
+ break;
+ }
+
+ break;
+ }
+
+ case InfoType.DebuggerAttached:
+ {
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (subId != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ value = KernelStatic.GetCurrentProcess().Debug ? 1UL : 0UL;
+
+ break;
+ }
+
+ case InfoType.ResourceLimit:
+ {
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (subId != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.ResourceLimit != null)
+ {
+ KHandleTable handleTable = currentProcess.HandleTable;
+ KResourceLimit resourceLimit = currentProcess.ResourceLimit;
+
+ Result result = handleTable.GenerateHandle(resourceLimit, out int resLimHandle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ value = (uint)resLimHandle;
+ }
+
+ break;
+ }
+
+ case InfoType.IdleTickCount:
+ {
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ int currentCore = KernelStatic.GetCurrentThread().CurrentCore;
+
+ if (subId != -1 && subId != currentCore)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ value = (ulong)KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks);
+
+ break;
+ }
+
+ case InfoType.RandomEntropy:
+ {
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if ((ulong)subId > 3)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ value = currentProcess.RandomEntropy[subId];
+
+ break;
+ }
+
+ case InfoType.ThreadTickCount:
+ {
+ if (subId < -1 || subId > 3)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KThread thread = KernelStatic.GetCurrentProcess().HandleTable.GetKThread(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ int currentCore = currentThread.CurrentCore;
+
+ if (subId != -1 && subId != currentCore)
+ {
+ return Result.Success;
+ }
+
+ KScheduler scheduler = _context.Schedulers[currentCore];
+
+ long timeDelta = PerformanceCounter.ElapsedTicks - scheduler.LastContextSwitchTime;
+
+ if (subId != -1)
+ {
+ value = (ulong)KTimeManager.ConvertHostTicksToTicks(timeDelta);
+ }
+ else
+ {
+ long totalTimeRunning = thread.TotalTimeRunning;
+
+ if (thread == currentThread)
+ {
+ totalTimeRunning += timeDelta;
+ }
+
+ value = (ulong)KTimeManager.ConvertHostTicksToTicks(totalTimeRunning);
+ }
+
+ break;
+ }
+
+ case InfoType.MesosphereCurrentProcess:
+ {
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if ((ulong)subId != 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+ KHandleTable handleTable = currentProcess.HandleTable;
+
+ Result result = handleTable.GenerateHandle(currentProcess, out int outHandle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ value = (ulong)outHandle;
+
+ break;
+ }
+
+ default: return KernelResult.InvalidEnumValue;
+ }
+
+ return Result.Success;
+ }
+
+ [Svc(0x45)]
+ public Result CreateEvent(out int wEventHandle, out int rEventHandle)
+ {
+ KEvent Event = new KEvent(_context);
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ Result result = process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle);
+
+ if (result == Result.Success)
+ {
+ result = process.HandleTable.GenerateHandle(Event.ReadableEvent, out rEventHandle);
+
+ if (result != Result.Success)
+ {
+ process.HandleTable.CloseHandle(wEventHandle);
+ }
+ }
+ else
+ {
+ rEventHandle = 0;
+ }
+
+ return result;
+ }
+
+ [Svc(0x65)]
+ public Result GetProcessList(out int count, [PointerSized] ulong address, int maxCount)
+ {
+ count = 0;
+
+ if ((maxCount >> 28) != 0)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ if (maxCount != 0)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ ulong copySize = (ulong)maxCount * 8;
+
+ if (address + copySize <= address)
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (currentProcess.MemoryManager.OutsideAddrSpace(address, copySize))
+ {
+ return KernelResult.InvalidMemState;
+ }
+ }
+
+ int copyCount = 0;
+
+ lock (_context.Processes)
+ {
+ foreach (KProcess process in _context.Processes.Values)
+ {
+ if (copyCount < maxCount)
+ {
+ if (!KernelTransfer.KernelToUser(address + (ulong)copyCount * 8, process.Pid))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+ }
+
+ copyCount++;
+ }
+ }
+
+ count = copyCount;
+
+ return Result.Success;
+ }
+
+ [Svc(0x6f)]
+ public Result GetSystemInfo(out long value, uint id, int handle, long subId)
+ {
+ value = 0;
+
+ if (id > 2)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ if (handle != 0)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (id < 2)
+ {
+ if ((ulong)subId > 3)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ KMemoryRegionManager region = _context.MemoryManager.MemoryRegions[subId];
+
+ switch (id)
+ {
+ // Memory region capacity.
+ case 0: value = (long)region.Size; break;
+
+ // Memory region free space.
+ case 1:
+ {
+ ulong freePagesCount = region.GetFreePages();
+
+ value = (long)(freePagesCount * KPageTableBase.PageSize);
+
+ break;
+ }
+ }
+ }
+ else /* if (Id == 2) */
+ {
+ if ((ulong)subId > 1)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ switch (subId)
+ {
+ case 0: value = _context.PrivilegedProcessLowestId; break;
+ case 1: value = _context.PrivilegedProcessHighestId; break;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ [Svc(0x30)]
+ public Result GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource)
+ {
+ limitValue = 0;
+
+ if (resource >= LimitableResource.Count)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KResourceLimit>(handle);
+
+ if (resourceLimit == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ limitValue = resourceLimit.GetLimitValue(resource);
+
+ return Result.Success;
+ }
+
+ [Svc(0x31)]
+ public Result GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource)
+ {
+ limitValue = 0;
+
+ if (resource >= LimitableResource.Count)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KResourceLimit>(handle);
+
+ if (resourceLimit == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ limitValue = resourceLimit.GetCurrentValue(resource);
+
+ return Result.Success;
+ }
+
+ [Svc(0x37)]
+ public Result GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource)
+ {
+ peak = 0;
+
+ if (resource >= LimitableResource.Count)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KResourceLimit>(handle);
+
+ if (resourceLimit == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ peak = resourceLimit.GetPeakValue(resource);
+
+ return Result.Success;
+ }
+
+ [Svc(0x7d)]
+ public Result CreateResourceLimit(out int handle)
+ {
+ KResourceLimit limit = new KResourceLimit(_context);
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ return process.HandleTable.GenerateHandle(limit, out handle);
+ }
+
+ [Svc(0x7e)]
+ public Result SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue)
+ {
+ if (resource >= LimitableResource.Count)
+ {
+ return KernelResult.InvalidEnumValue;
+ }
+
+ KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KResourceLimit>(handle);
+
+ if (resourceLimit == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ return resourceLimit.SetLimitValue(resource, limitValue);
+ }
+
+ // Thread
+
+ [Svc(8)]
+ public Result CreateThread(
+ out int handle,
+ [PointerSized] ulong entrypoint,
+ [PointerSized] ulong argsPtr,
+ [PointerSized] ulong stackTop,
+ int priority,
+ int cpuCore)
+ {
+ return CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore, null);
+ }
+
+ public Result CreateThread(
+ out int handle,
+ ulong entrypoint,
+ ulong argsPtr,
+ ulong stackTop,
+ int priority,
+ int cpuCore,
+ ThreadStart customThreadStart)
+ {
+ handle = 0;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (cpuCore == -2)
+ {
+ cpuCore = currentProcess.DefaultCpuCore;
+ }
+
+ if ((uint)cpuCore >= KScheduler.CpuCoresCount || !currentProcess.IsCpuCoreAllowed(cpuCore))
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+
+ if ((uint)priority >= KScheduler.PrioritiesCount || !currentProcess.IsPriorityAllowed(priority))
+ {
+ return KernelResult.InvalidPriority;
+ }
+
+ long timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100);
+
+ if (currentProcess.ResourceLimit != null &&
+ !currentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, timeout))
+ {
+ return KernelResult.ResLimitExceeded;
+ }
+
+ KThread thread = new KThread(_context);
+
+ Result result = currentProcess.InitializeThread(
+ thread,
+ entrypoint,
+ argsPtr,
+ stackTop,
+ priority,
+ cpuCore,
+ customThreadStart);
+
+ if (result == Result.Success)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ result = process.HandleTable.GenerateHandle(thread, out handle);
+ }
+ else
+ {
+ currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1);
+ }
+
+ thread.DecrementReferenceCount();
+
+ return result;
+ }
+
+ [Svc(9)]
+ public Result StartThread(int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread != null)
+ {
+ thread.IncrementReferenceCount();
+
+ Result result = thread.Start();
+
+ if (result == Result.Success)
+ {
+ thread.IncrementReferenceCount();
+ }
+
+ thread.DecrementReferenceCount();
+
+ return result;
+ }
+ else
+ {
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ [Svc(0xa)]
+ public void ExitThread()
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ currentThread.Exit();
+ }
+
+ [Svc(0xb)]
+ public void SleepThread(long timeout)
+ {
+ if (timeout < 1)
+ {
+ switch (timeout)
+ {
+ case 0: KScheduler.Yield(_context); break;
+ case -1: KScheduler.YieldWithLoadBalancing(_context); break;
+ case -2: KScheduler.YieldToAnyThread(_context); break;
+ }
+ }
+ else
+ {
+ KernelStatic.GetCurrentThread().Sleep(timeout + KTimeManager.DefaultTimeIncrementNanoseconds);
+ }
+ }
+
+ [Svc(0xc)]
+ public Result GetThreadPriority(out int priority, int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread != null)
+ {
+ priority = thread.DynamicPriority;
+
+ return Result.Success;
+ }
+ else
+ {
+ priority = 0;
+
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ [Svc(0xd)]
+ public Result SetThreadPriority(int handle, int priority)
+ {
+ // TODO: NPDM check.
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ thread.SetPriority(priority);
+
+ return Result.Success;
+ }
+
+ [Svc(0xe)]
+ public Result GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread != null)
+ {
+ preferredCore = thread.PreferredCore;
+ affinityMask = thread.AffinityMask;
+
+ return Result.Success;
+ }
+ else
+ {
+ preferredCore = 0;
+ affinityMask = 0;
+
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ [Svc(0xf)]
+ public Result SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (preferredCore == -2)
+ {
+ preferredCore = currentProcess.DefaultCpuCore;
+
+ affinityMask = 1UL << preferredCore;
+ }
+ else
+ {
+ if ((currentProcess.Capabilities.AllowedCpuCoresMask | affinityMask) !=
+ currentProcess.Capabilities.AllowedCpuCoresMask)
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+
+ if (affinityMask == 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+
+ if ((uint)preferredCore > 3)
+ {
+ if ((preferredCore | 2) != -1)
+ {
+ return KernelResult.InvalidCpuCore;
+ }
+ }
+ else if ((affinityMask & (1UL << preferredCore)) == 0)
+ {
+ return KernelResult.InvalidCombination;
+ }
+ }
+
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ return thread.SetCoreAndAffinityMask(preferredCore, affinityMask);
+ }
+
+ [Svc(0x10)]
+ public int GetCurrentProcessorNumber()
+ {
+ return KernelStatic.GetCurrentThread().CurrentCore;
+ }
+
+ [Svc(0x25)]
+ public Result GetThreadId(out ulong threadUid, int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread != null)
+ {
+ threadUid = thread.ThreadUid;
+
+ return Result.Success;
+ }
+ else
+ {
+ threadUid = 0;
+
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ [Svc(0x32)]
+ public Result SetThreadActivity(int handle, bool pause)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetObject<KThread>(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (thread.Owner != process)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (thread == KernelStatic.GetCurrentThread())
+ {
+ return KernelResult.InvalidThread;
+ }
+
+ return thread.SetActivity(pause);
+ }
+
+ [Svc(0x33)]
+ public Result GetThreadContext3([PointerSized] ulong address, int handle)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ KThread thread = currentProcess.HandleTable.GetObject<KThread>(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (thread.Owner != currentProcess)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ if (currentThread == thread)
+ {
+ return KernelResult.InvalidThread;
+ }
+
+ Result result = thread.GetThreadContext3(out ThreadContext context);
+
+ if (result == Result.Success)
+ {
+ return KernelTransfer.KernelToUser(address, context)
+ ? Result.Success
+ : KernelResult.InvalidMemState;
+ }
+
+ return result;
+ }
+
+ // Thread synchronization
+
+ [Svc(0x18)]
+ public Result WaitSynchronization(out int handleIndex, [PointerSized] ulong handlesPtr, int handlesCount, long timeout)
+ {
+ handleIndex = 0;
+
+ if ((uint)handlesCount > KThread.MaxWaitSyncObjects)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (handlesCount != 0)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (currentProcess.MemoryManager.AddrSpaceStart > handlesPtr)
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ long handlesSize = handlesCount * 4;
+
+ if (handlesPtr + (ulong)handlesSize <= handlesPtr)
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ if (handlesPtr + (ulong)handlesSize - 1 > currentProcess.MemoryManager.AddrSpaceEnd - 1)
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ Span<int> handles = new Span<int>(currentThread.WaitSyncHandles).Slice(0, handlesCount);
+
+ if (!KernelTransfer.UserToKernelArray(handlesPtr, handles))
+ {
+ return KernelResult.UserCopyFailed;
+ }
+
+ return WaitSynchronization(out handleIndex, handles, timeout);
+ }
+
+ return WaitSynchronization(out handleIndex, ReadOnlySpan<int>.Empty, timeout);
+ }
+
+ public Result WaitSynchronization(out int handleIndex, ReadOnlySpan<int> handles, long timeout)
+ {
+ handleIndex = 0;
+
+ if ((uint)handles.Length > KThread.MaxWaitSyncObjects)
+ {
+ return KernelResult.MaximumExceeded;
+ }
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ var syncObjs = new Span<KSynchronizationObject>(currentThread.WaitSyncObjects).Slice(0, handles.Length);
+
+ if (handles.Length != 0)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ int processedHandles = 0;
+
+ for (; processedHandles < handles.Length; processedHandles++)
+ {
+ KSynchronizationObject syncObj = currentProcess.HandleTable.GetObject<KSynchronizationObject>(handles[processedHandles]);
+
+ if (syncObj == null)
+ {
+ break;
+ }
+
+ syncObjs[processedHandles] = syncObj;
+
+ syncObj.IncrementReferenceCount();
+ }
+
+ if (processedHandles != handles.Length)
+ {
+ // One or more handles are invalid.
+ for (int index = 0; index < processedHandles; index++)
+ {
+ currentThread.WaitSyncObjects[index].DecrementReferenceCount();
+ }
+
+ return KernelResult.InvalidHandle;
+ }
+ }
+
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ Result result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex);
+
+ if (result == KernelResult.PortRemoteClosed)
+ {
+ result = Result.Success;
+ }
+
+ for (int index = 0; index < handles.Length; index++)
+ {
+ currentThread.WaitSyncObjects[index].DecrementReferenceCount();
+ }
+
+ return result;
+ }
+
+ [Svc(0x19)]
+ public Result CancelSynchronization(int handle)
+ {
+ KProcess process = KernelStatic.GetCurrentProcess();
+
+ KThread thread = process.HandleTable.GetKThread(handle);
+
+ if (thread == null)
+ {
+ return KernelResult.InvalidHandle;
+ }
+
+ thread.CancelSynchronization();
+
+ return Result.Success;
+ }
+
+ [Svc(0x1a)]
+ public Result ArbitrateLock(int ownerHandle, [PointerSized] ulong mutexAddress, int requesterHandle)
+ {
+ if (IsPointingInsideKernel(mutexAddress))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (IsAddressNotWordAligned(mutexAddress))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
+ }
+
+ [Svc(0x1b)]
+ public Result ArbitrateUnlock([PointerSized] ulong mutexAddress)
+ {
+ if (IsPointingInsideKernel(mutexAddress))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (IsAddressNotWordAligned(mutexAddress))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress);
+ }
+
+ [Svc(0x1c)]
+ public Result WaitProcessWideKeyAtomic(
+ [PointerSized] ulong mutexAddress,
+ [PointerSized] ulong condVarAddress,
+ int handle,
+ long timeout)
+ {
+ if (IsPointingInsideKernel(mutexAddress))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (IsAddressNotWordAligned(mutexAddress))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic(
+ mutexAddress,
+ condVarAddress,
+ handle,
+ timeout);
+ }
+
+ [Svc(0x1d)]
+ public Result SignalProcessWideKey([PointerSized] ulong address, int count)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ currentProcess.AddressArbiter.SignalProcessWideKey(address, count);
+
+ return Result.Success;
+ }
+
+ [Svc(0x34)]
+ public Result WaitForAddress([PointerSized] ulong address, ArbitrationType type, int value, long timeout)
+ {
+ if (IsPointingInsideKernel(address))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (IsAddressNotWordAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (timeout > 0)
+ {
+ timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
+ }
+
+ return type switch
+ {
+ ArbitrationType.WaitIfLessThan
+ => currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, false, timeout),
+ ArbitrationType.DecrementAndWaitIfLessThan
+ => currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, true, timeout),
+ ArbitrationType.WaitIfEqual
+ => currentProcess.AddressArbiter.WaitForAddressIfEqual(address, value, timeout),
+ _ => KernelResult.InvalidEnumValue,
+ };
+ }
+
+ [Svc(0x35)]
+ public Result SignalToAddress([PointerSized] ulong address, SignalType type, int value, int count)
+ {
+ if (IsPointingInsideKernel(address))
+ {
+ return KernelResult.InvalidMemState;
+ }
+
+ if (IsAddressNotWordAligned(address))
+ {
+ return KernelResult.InvalidAddress;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ return type switch
+ {
+ SignalType.Signal
+ => currentProcess.AddressArbiter.Signal(address, count),
+ SignalType.SignalAndIncrementIfEqual
+ => currentProcess.AddressArbiter.SignalAndIncrementIfEqual(address, value, count),
+ SignalType.SignalAndModifyIfEqual
+ => currentProcess.AddressArbiter.SignalAndModifyIfEqual(address, value, count),
+ _ => KernelResult.InvalidEnumValue
+ };
+ }
+
+ [Svc(0x36)]
+ public Result SynchronizePreemptionState()
+ {
+ KernelStatic.GetCurrentThread().SynchronizePreemptionState();
+
+ return Result.Success;
+ }
+
+ private static bool IsPointingInsideKernel(ulong address)
+ {
+ return (address + 0x1000000000) < 0xffffff000;
+ }
+
+ private static bool IsAddressNotWordAligned(ulong address)
+ {
+ return (address & 3) != 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs
new file mode 100644
index 00000000..710bac94
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs
@@ -0,0 +1,44 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ partial class SyscallHandler
+ {
+ private readonly KernelContext _context;
+
+ public SyscallHandler(KernelContext context)
+ {
+ _context = context;
+ }
+
+ public void SvcCall(IExecutionContext context, ulong address, int id)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.Owner != null &&
+ currentThread.GetUserDisableCount() != 0 &&
+ currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null)
+ {
+ _context.CriticalSection.Enter();
+
+ currentThread.Owner.PinThread(currentThread);
+
+ currentThread.SetUserInterruptFlag();
+
+ _context.CriticalSection.Leave();
+ }
+
+ if (context.IsAarch32)
+ {
+ SyscallDispatch.Dispatch32(_context.Syscall, context, id);
+ }
+ else
+ {
+ SyscallDispatch.Dispatch64(_context.Syscall, context, id);
+ }
+
+ currentThread.HandlePostSyscall();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs
new file mode 100644
index 00000000..b524406a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs
@@ -0,0 +1,22 @@
+using ARMeilleure.State;
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+ struct ThreadContext
+ {
+ public Array29<ulong> Registers;
+ public ulong Fp;
+ public ulong Lr;
+ public ulong Sp;
+ public ulong Pc;
+ public uint Pstate;
+#pragma warning disable CS0169
+ private uint _padding;
+#pragma warning restore CS0169
+ public Array32<V128> FpuRegisters;
+ public uint Fpcr;
+ public uint Fpsr;
+ public ulong Tpidr;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
new file mode 100644
index 00000000..89c1bf1f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ArbitrationType
+ {
+ WaitIfLessThan = 0,
+ DecrementAndWaitIfLessThan = 1,
+ WaitIfEqual = 2
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
new file mode 100644
index 00000000..74867b44
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
@@ -0,0 +1,581 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KAddressArbiter
+ {
+ private const int HasListenersMask = 0x40000000;
+
+ private readonly KernelContext _context;
+
+ private readonly List<KThread> _condVarThreads;
+ private readonly List<KThread> _arbiterThreads;
+
+ public KAddressArbiter(KernelContext context)
+ {
+ _context = context;
+
+ _condVarThreads = new List<KThread>();
+ _arbiterThreads = new List<KThread>();
+ }
+
+ public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.TerminationRequested)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = Result.Success;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!KernelTransfer.UserToKernel(out int mutexValue, mutexAddress))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (mutexValue != (ownerHandle | HasListenersMask))
+ {
+ _context.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(ownerHandle);
+
+ if (mutexOwner == null)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidHandle;
+ }
+
+ currentThread.MutexAddress = mutexAddress;
+ currentThread.ThreadHandleForUserMutex = requesterHandle;
+
+ mutexOwner.AddMutexWaiter(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ _context.CriticalSection.Leave();
+ _context.CriticalSection.Enter();
+
+ if (currentThread.MutexOwner != null)
+ {
+ currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
+ }
+
+ _context.CriticalSection.Leave();
+
+ return currentThread.ObjSyncResult;
+ }
+
+ public Result ArbitrateUnlock(ulong mutexAddress)
+ {
+ _context.CriticalSection.Enter();
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ (int mutexValue, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress);
+
+ Result result = Result.Success;
+
+ if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue))
+ {
+ result = KernelResult.InvalidMemState;
+ }
+
+ if (result != Result.Success && newOwnerThread != null)
+ {
+ newOwnerThread.SignaledObj = null;
+ newOwnerThread.ObjSyncResult = result;
+ }
+
+ _context.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public Result WaitProcessWideKeyAtomic(ulong mutexAddress, ulong condVarAddress, int threadHandle, long timeout)
+ {
+ _context.CriticalSection.Enter();
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ if (currentThread.TerminationRequested)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ (int mutexValue, _) = MutexUnlock(currentThread, mutexAddress);
+
+ KernelTransfer.KernelToUser(condVarAddress, 1);
+
+ if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ currentThread.MutexAddress = mutexAddress;
+ currentThread.ThreadHandleForUserMutex = threadHandle;
+ currentThread.CondVarAddress = condVarAddress;
+
+ _condVarThreads.Add(currentThread);
+
+ if (timeout != 0)
+ {
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+ }
+
+ _context.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.MutexOwner != null)
+ {
+ currentThread.MutexOwner.RemoveMutexWaiter(currentThread);
+ }
+
+ _condVarThreads.Remove(currentThread);
+
+ _context.CriticalSection.Leave();
+
+ return currentThread.ObjSyncResult;
+ }
+
+ private (int, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress)
+ {
+ KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count);
+
+ int mutexValue = 0;
+
+ if (newOwnerThread != null)
+ {
+ mutexValue = newOwnerThread.ThreadHandleForUserMutex;
+
+ if (count >= 2)
+ {
+ mutexValue |= HasListenersMask;
+ }
+
+ newOwnerThread.SignaledObj = null;
+ newOwnerThread.ObjSyncResult = Result.Success;
+
+ newOwnerThread.ReleaseAndResume();
+ }
+
+ return (mutexValue, newOwnerThread);
+ }
+
+ public void SignalProcessWideKey(ulong address, int count)
+ {
+ _context.CriticalSection.Enter();
+
+ WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address);
+
+ if (!_condVarThreads.Any(x => x.CondVarAddress == address))
+ {
+ KernelTransfer.KernelToUser(address, 0);
+ }
+
+ _context.CriticalSection.Leave();
+ }
+
+ private static void TryAcquireMutex(KThread requester)
+ {
+ ulong address = requester.MutexAddress;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.CpuMemory.IsMapped(address))
+ {
+ // Invalid address.
+ requester.SignaledObj = null;
+ requester.ObjSyncResult = KernelResult.InvalidMemState;
+
+ return;
+ }
+
+ ref int mutexRef = ref currentProcess.CpuMemory.GetRef<int>(address);
+
+ int mutexValue, newMutexValue;
+
+ do
+ {
+ mutexValue = mutexRef;
+
+ if (mutexValue != 0)
+ {
+ // Update value to indicate there is a mutex waiter now.
+ newMutexValue = mutexValue | HasListenersMask;
+ }
+ else
+ {
+ // No thread owning the mutex, assign to requesting thread.
+ newMutexValue = requester.ThreadHandleForUserMutex;
+ }
+ }
+ while (Interlocked.CompareExchange(ref mutexRef, newMutexValue, mutexValue) != mutexValue);
+
+ if (mutexValue == 0)
+ {
+ // We now own the mutex.
+ requester.SignaledObj = null;
+ requester.ObjSyncResult = Result.Success;
+
+ requester.ReleaseAndResume();
+
+ return;
+ }
+
+ mutexValue &= ~HasListenersMask;
+
+ KThread mutexOwner = currentProcess.HandleTable.GetObject<KThread>(mutexValue);
+
+ if (mutexOwner != null)
+ {
+ // Mutex already belongs to another thread, wait for it.
+ mutexOwner.AddMutexWaiter(requester);
+ }
+ else
+ {
+ // Invalid mutex owner.
+ requester.SignaledObj = null;
+ requester.ObjSyncResult = KernelResult.InvalidHandle;
+
+ requester.ReleaseAndResume();
+ }
+ }
+
+ public Result WaitForAddressIfEqual(ulong address, int value, long timeout)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.TerminationRequested)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ if (!KernelTransfer.UserToKernel(out int currentValue, address))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (currentValue == value)
+ {
+ if (timeout == 0)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.TimedOut;
+ }
+
+ currentThread.MutexAddress = address;
+ currentThread.WaitingInArbitration = true;
+
+ _arbiterThreads.Add(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _context.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.WaitingInArbitration)
+ {
+ _arbiterThreads.Remove(currentThread);
+
+ currentThread.WaitingInArbitration = false;
+ }
+
+ _context.CriticalSection.Leave();
+
+ return currentThread.ObjSyncResult;
+ }
+
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ public Result WaitForAddressIfLessThan(ulong address, int value, bool shouldDecrement, long timeout)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.TerminationRequested)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = KernelResult.TimedOut;
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!KernelTransfer.UserToKernel(out int currentValue, address))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ if (shouldDecrement)
+ {
+ currentValue = Interlocked.Decrement(ref currentProcess.CpuMemory.GetRef<int>(address)) + 1;
+ }
+
+ if (currentValue < value)
+ {
+ if (timeout == 0)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.TimedOut;
+ }
+
+ currentThread.MutexAddress = address;
+ currentThread.WaitingInArbitration = true;
+
+ _arbiterThreads.Add(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _context.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _context.CriticalSection.Enter();
+
+ if (currentThread.WaitingInArbitration)
+ {
+ _arbiterThreads.Remove(currentThread);
+
+ currentThread.WaitingInArbitration = false;
+ }
+
+ _context.CriticalSection.Leave();
+
+ return currentThread.ObjSyncResult;
+ }
+
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ public Result Signal(ulong address, int count)
+ {
+ _context.CriticalSection.Enter();
+
+ WakeArbiterThreads(address, count);
+
+ _context.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ public Result SignalAndIncrementIfEqual(ulong address, int value, int count)
+ {
+ _context.CriticalSection.Enter();
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.CpuMemory.IsMapped(address))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address);
+
+ int currentValue;
+
+ do
+ {
+ currentValue = valueRef;
+
+ if (currentValue != value)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+ }
+ while (Interlocked.CompareExchange(ref valueRef, currentValue + 1, currentValue) != currentValue);
+
+ WakeArbiterThreads(address, count);
+
+ _context.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ public Result SignalAndModifyIfEqual(ulong address, int value, int count)
+ {
+ _context.CriticalSection.Enter();
+
+ int addend;
+
+ // The value is decremented if the number of threads waiting is less
+ // or equal to the Count of threads to be signaled, or Count is zero
+ // or negative. It is incremented if there are no threads waiting.
+ int waitingCount = 0;
+
+ foreach (KThread thread in _arbiterThreads.Where(x => x.MutexAddress == address))
+ {
+ if (++waitingCount >= count)
+ {
+ break;
+ }
+ }
+
+ if (waitingCount > 0)
+ {
+ if (count <= 0)
+ {
+ addend = -2;
+ }
+ else if (waitingCount < count)
+ {
+ addend = -1;
+ }
+ else
+ {
+ addend = 0;
+ }
+ }
+ else
+ {
+ addend = 1;
+ }
+
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+
+ if (!currentProcess.CpuMemory.IsMapped(address))
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidMemState;
+ }
+
+ ref int valueRef = ref currentProcess.CpuMemory.GetRef<int>(address);
+
+ int currentValue;
+
+ do
+ {
+ currentValue = valueRef;
+
+ if (currentValue != value)
+ {
+ _context.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+ }
+ while (Interlocked.CompareExchange(ref valueRef, currentValue + addend, currentValue) != currentValue);
+
+ WakeArbiterThreads(address, count);
+
+ _context.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ private void WakeArbiterThreads(ulong address, int count)
+ {
+ static void RemoveArbiterThread(KThread thread)
+ {
+ thread.SignaledObj = null;
+ thread.ObjSyncResult = Result.Success;
+
+ thread.ReleaseAndResume();
+
+ thread.WaitingInArbitration = false;
+ }
+
+ WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address);
+ }
+
+ private static void WakeThreads(
+ List<KThread> threads,
+ int count,
+ Action<KThread> removeCallback,
+ Func<KThread, bool> predicate)
+ {
+ var candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority);
+ var toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray();
+
+ foreach (KThread thread in toSignal)
+ {
+ removeCallback(thread);
+ threads.Remove(thread);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
new file mode 100644
index 00000000..891e632f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ static class KConditionVariable
+ {
+ public static void Wait(KernelContext context, LinkedList<KThread> threadList, object mutex, long timeout)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ context.CriticalSection.Enter();
+
+ Monitor.Exit(mutex);
+
+ currentThread.Withholder = threadList;
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ currentThread.WithholderNode = threadList.AddLast(currentThread);
+
+ if (currentThread.TerminationRequested)
+ {
+ threadList.Remove(currentThread.WithholderNode);
+
+ currentThread.Reschedule(ThreadSchedState.Running);
+
+ currentThread.Withholder = null;
+
+ context.CriticalSection.Leave();
+ }
+ else
+ {
+ if (timeout > 0)
+ {
+ context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ context.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ context.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+ }
+
+ Monitor.Enter(mutex);
+ }
+
+ public static void NotifyAll(KernelContext context, LinkedList<KThread> threadList)
+ {
+ context.CriticalSection.Enter();
+
+ LinkedListNode<KThread> node = threadList.First;
+
+ for (; node != null; node = threadList.First)
+ {
+ KThread thread = node.Value;
+
+ threadList.Remove(thread.WithholderNode);
+
+ thread.Withholder = null;
+
+ thread.Reschedule(ThreadSchedState.Running);
+ }
+
+ context.CriticalSection.Leave();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
new file mode 100644
index 00000000..1d61f2f0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
@@ -0,0 +1,64 @@
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KCriticalSection
+ {
+ private readonly KernelContext _context;
+ private readonly object _lock;
+ private int _recursionCount;
+
+ public object Lock => _lock;
+
+ public KCriticalSection(KernelContext context)
+ {
+ _context = context;
+ _lock = new object();
+ }
+
+ public void Enter()
+ {
+ Monitor.Enter(_lock);
+
+ _recursionCount++;
+ }
+
+ public void Leave()
+ {
+ if (_recursionCount == 0)
+ {
+ return;
+ }
+
+ if (--_recursionCount == 0)
+ {
+ ulong scheduledCoresMask = KScheduler.SelectThreads(_context);
+
+ Monitor.Exit(_lock);
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+ bool isCurrentThreadSchedulable = currentThread != null && currentThread.IsSchedulable;
+ if (isCurrentThreadSchedulable)
+ {
+ KScheduler.EnableScheduling(_context, scheduledCoresMask);
+ }
+ else
+ {
+ KScheduler.EnableSchedulingFromForeignThread(_context, scheduledCoresMask);
+
+ // If the thread exists but is not schedulable, we still want to suspend
+ // it if it's not runnable. That allows the kernel to still block HLE threads
+ // even if they are not scheduled on guest cores.
+ if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
+ {
+ currentThread.SchedulerWaitEvent.WaitOne();
+ }
+ }
+ }
+ else
+ {
+ Monitor.Exit(_lock);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
new file mode 100644
index 00000000..da3e217b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KEvent
+ {
+ public KReadableEvent ReadableEvent { get; private set; }
+ public KWritableEvent WritableEvent { get; private set; }
+
+ public KEvent(KernelContext context)
+ {
+ ReadableEvent = new KReadableEvent(context, this);
+ WritableEvent = new KWritableEvent(context, this);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs
new file mode 100644
index 00000000..14fba704
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs
@@ -0,0 +1,286 @@
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KPriorityQueue
+ {
+ private readonly LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
+ private readonly LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
+
+ private readonly long[] _scheduledPrioritiesPerCore;
+ private readonly long[] _suggestedPrioritiesPerCore;
+
+ public KPriorityQueue()
+ {
+ _suggestedThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
+ _scheduledThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
+
+ for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++)
+ {
+ _suggestedThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
+ _scheduledThreadsPerPrioPerCore[prio] = new LinkedList<KThread>[KScheduler.CpuCoresCount];
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
+ _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList<KThread>();
+ }
+ }
+
+ _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
+ _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
+ }
+
+ public readonly ref struct KThreadEnumerable
+ {
+ readonly LinkedList<KThread>[][] _listPerPrioPerCore;
+ readonly long[] _prios;
+ readonly int _core;
+
+ public KThreadEnumerable(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
+ {
+ _listPerPrioPerCore = listPerPrioPerCore;
+ _prios = prios;
+ _core = core;
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(_listPerPrioPerCore, _prios, _core);
+ }
+
+ public ref struct Enumerator
+ {
+ private readonly LinkedList<KThread>[][] _listPerPrioPerCore;
+ private readonly int _core;
+ private long _prioMask;
+ private int _prio;
+ private LinkedList<KThread> _list;
+ private LinkedListNode<KThread> _node;
+
+ public Enumerator(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
+ {
+ _listPerPrioPerCore = listPerPrioPerCore;
+ _core = core;
+ _prioMask = prios[core];
+ _prio = BitOperations.TrailingZeroCount(_prioMask);
+ _prioMask &= ~(1L << _prio);
+ }
+
+ public KThread Current => _node?.Value;
+
+ public bool MoveNext()
+ {
+ _node = _node?.Next;
+
+ if (_node == null)
+ {
+ if (!MoveNextListAndFirstNode())
+ {
+ return false;
+ }
+ }
+
+ return _node != null;
+ }
+
+ private bool MoveNextListAndFirstNode()
+ {
+ if (_prio < KScheduler.PrioritiesCount)
+ {
+ _list = _listPerPrioPerCore[_prio][_core];
+
+ _node = _list.First;
+
+ _prio = BitOperations.TrailingZeroCount(_prioMask);
+
+ _prioMask &= ~(1L << _prio);
+
+ return true;
+ }
+ else
+ {
+ _list = null;
+ _node = null;
+ return false;
+ }
+ }
+ }
+ }
+
+ public KThreadEnumerable ScheduledThreads(int core)
+ {
+ return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
+ }
+
+ public KThreadEnumerable SuggestedThreads(int core)
+ {
+ return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
+ }
+
+ public KThread ScheduledThreadsFirstOrDefault(int core)
+ {
+ return ScheduledThreadsElementAtOrDefault(core, 0);
+ }
+
+ public KThread ScheduledThreadsElementAtOrDefault(int core, int index)
+ {
+ int currentIndex = 0;
+ foreach (var scheduledThread in ScheduledThreads(core))
+ {
+ if (currentIndex == index)
+ {
+ return scheduledThread;
+ }
+ else
+ {
+ currentIndex++;
+ }
+ }
+
+ return null;
+ }
+
+ public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority)
+ {
+ foreach (var scheduledThread in ScheduledThreads(core))
+ {
+ if (scheduledThread.DynamicPriority == dynamicPriority)
+ {
+ return scheduledThread;
+ }
+ }
+
+ return null;
+ }
+
+ public bool HasScheduledThreads(int core)
+ {
+ return ScheduledThreadsFirstOrDefault(core) != null;
+ }
+
+ public void TransferToCore(int prio, int dstCore, KThread thread)
+ {
+ int srcCore = thread.ActiveCore;
+ if (srcCore == dstCore)
+ {
+ return;
+ }
+
+ thread.ActiveCore = dstCore;
+
+ if (srcCore >= 0)
+ {
+ Unschedule(prio, srcCore, thread);
+ }
+
+ if (dstCore >= 0)
+ {
+ Unsuggest(prio, dstCore, thread);
+ Schedule(prio, dstCore, thread);
+ }
+
+ if (srcCore >= 0)
+ {
+ Suggest(prio, srcCore, thread);
+ }
+ }
+
+ public void Suggest(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
+
+ _suggestedPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public void Unsuggest(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ LinkedList<KThread> queue = SuggestedQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ if (queue.First == null)
+ {
+ _suggestedPrioritiesPerCore[core] &= ~(1L << prio);
+ }
+ }
+
+ public void Schedule(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
+
+ _scheduledPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public void SchedulePrepend(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
+
+ _scheduledPrioritiesPerCore[core] |= 1L << prio;
+ }
+
+ public KThread Reschedule(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return null;
+ }
+
+ LinkedList<KThread> queue = ScheduledQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ thread.SiblingsPerCore[core] = queue.AddLast(thread);
+
+ return queue.First.Value;
+ }
+
+ public void Unschedule(int prio, int core, KThread thread)
+ {
+ if (prio >= KScheduler.PrioritiesCount)
+ {
+ return;
+ }
+
+ LinkedList<KThread> queue = ScheduledQueue(prio, core);
+
+ queue.Remove(thread.SiblingsPerCore[core]);
+
+ if (queue.First == null)
+ {
+ _scheduledPrioritiesPerCore[core] &= ~(1L << prio);
+ }
+ }
+
+ private LinkedList<KThread> SuggestedQueue(int prio, int core)
+ {
+ return _suggestedThreadsPerPrioPerCore[prio][core];
+ }
+
+ private LinkedList<KThread> ScheduledQueue(int prio, int core)
+ {
+ return _scheduledThreadsPerPrioPerCore[prio][core];
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
new file mode 100644
index 00000000..d9e7befa
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs
@@ -0,0 +1,65 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KReadableEvent : KSynchronizationObject
+ {
+ private readonly KEvent _parent;
+
+ private bool _signaled;
+
+ public KReadableEvent(KernelContext context, KEvent parent) : base(context)
+ {
+ _parent = parent;
+ }
+
+ public override void Signal()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (!_signaled)
+ {
+ _signaled = true;
+
+ base.Signal();
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public Result Clear()
+ {
+ _signaled = false;
+
+ return Result.Success;
+ }
+
+ public Result ClearIfSignaled()
+ {
+ Result result;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (_signaled)
+ {
+ _signaled = false;
+
+ result = Result.Success;
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _signaled;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
new file mode 100644
index 00000000..b9de7d9c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -0,0 +1,661 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System;
+using System.Numerics;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ partial class KScheduler : IDisposable
+ {
+ public const int PrioritiesCount = 64;
+ public const int CpuCoresCount = 4;
+
+ private const int RoundRobinTimeQuantumMs = 10;
+
+ private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
+
+ private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
+
+ private readonly KernelContext _context;
+ private readonly int _coreId;
+
+ private struct SchedulingState
+ {
+ public volatile bool NeedsScheduling;
+ public volatile KThread SelectedThread;
+ }
+
+ private SchedulingState _state;
+
+ private AutoResetEvent _idleInterruptEvent;
+ private readonly object _idleInterruptEventLock;
+
+ private KThread _previousThread;
+ private KThread _currentThread;
+ private readonly KThread _idleThread;
+
+ public KThread PreviousThread => _previousThread;
+ public KThread CurrentThread => _currentThread;
+ public long LastContextSwitchTime { get; private set; }
+ public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
+
+ public KScheduler(KernelContext context, int coreId)
+ {
+ _context = context;
+ _coreId = coreId;
+
+ _idleInterruptEvent = new AutoResetEvent(false);
+ _idleInterruptEventLock = new object();
+
+ KThread idleThread = CreateIdleThread(context, coreId);
+
+ _currentThread = idleThread;
+ _idleThread = idleThread;
+
+ idleThread.StartHostThread();
+ idleThread.SchedulerWaitEvent.Set();
+ }
+
+ private KThread CreateIdleThread(KernelContext context, int cpuCore)
+ {
+ KThread idleThread = new KThread(context);
+
+ idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop);
+
+ return idleThread;
+ }
+
+ public static ulong SelectThreads(KernelContext context)
+ {
+ if (context.ThreadReselectionRequested)
+ {
+ return SelectThreadsImpl(context);
+ }
+ else
+ {
+ return 0UL;
+ }
+ }
+
+ private static ulong SelectThreadsImpl(KernelContext context)
+ {
+ context.ThreadReselectionRequested = false;
+
+ ulong scheduledCoresMask = 0UL;
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
+
+ if (thread != null &&
+ thread.Owner != null &&
+ thread.Owner.PinnedThreads[core] != null &&
+ thread.Owner.PinnedThreads[core] != thread)
+ {
+ KThread candidate = thread.Owner.PinnedThreads[core];
+
+ if (candidate.KernelWaitersCount == 0 && !thread.Owner.IsExceptionUserThread(candidate))
+ {
+ if (candidate.SchedFlags == ThreadSchedState.Running)
+ {
+ thread = candidate;
+ }
+ else
+ {
+ thread = null;
+ }
+ }
+ }
+
+ scheduledCoresMask |= context.Schedulers[core].SelectThread(thread);
+ }
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ // If the core is not idle (there's already a thread running on it),
+ // then we don't need to attempt load balancing.
+ if (context.PriorityQueue.HasScheduledThreads(core))
+ {
+ continue;
+ }
+
+ Array.Fill(_srcCoresHighestPrioThreads, 0);
+
+ int srcCoresHighestPrioThreadsCount = 0;
+
+ KThread dst = null;
+
+ // Select candidate threads that could run on this core.
+ // Give preference to threads that are not yet selected.
+ foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+ {
+ if (suggested.ActiveCore < 0 || suggested != context.Schedulers[suggested.ActiveCore]._state.SelectedThread)
+ {
+ dst = suggested;
+ break;
+ }
+
+ _srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
+ }
+
+ // Not yet selected candidate found.
+ if (dst != null)
+ {
+ // Priorities < 2 are used for the kernel message dispatching
+ // threads, we should skip load balancing entirely.
+ if (dst.DynamicPriority >= 2)
+ {
+ context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
+
+ scheduledCoresMask |= context.Schedulers[core].SelectThread(dst);
+ }
+
+ continue;
+ }
+
+ // All candidates are already selected, choose the best one
+ // (the first one that doesn't make the source core idle if moved).
+ for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
+ {
+ int srcCore = _srcCoresHighestPrioThreads[index];
+
+ KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1);
+
+ if (src != null)
+ {
+ // Run the second thread on the queue on the source core,
+ // move the first one to the current core.
+ KThread origSelectedCoreSrc = context.Schedulers[srcCore]._state.SelectedThread;
+
+ scheduledCoresMask |= context.Schedulers[srcCore].SelectThread(src);
+
+ context.PriorityQueue.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc);
+
+ scheduledCoresMask |= context.Schedulers[core].SelectThread(origSelectedCoreSrc);
+ }
+ }
+ }
+
+ return scheduledCoresMask;
+ }
+
+ private ulong SelectThread(KThread nextThread)
+ {
+ KThread previousThread = _state.SelectedThread;
+
+ if (previousThread != nextThread)
+ {
+ if (previousThread != null)
+ {
+ previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
+ }
+
+ _state.SelectedThread = nextThread;
+ _state.NeedsScheduling = true;
+ return 1UL << _coreId;
+ }
+ else
+ {
+ return 0UL;
+ }
+ }
+
+ public static void EnableScheduling(KernelContext context, ulong scheduledCoresMask)
+ {
+ KScheduler currentScheduler = context.Schedulers[KernelStatic.GetCurrentThread().CurrentCore];
+
+ // Note that "RescheduleCurrentCore" will block, so "RescheduleOtherCores" must be done first.
+ currentScheduler.RescheduleOtherCores(scheduledCoresMask);
+ currentScheduler.RescheduleCurrentCore();
+ }
+
+ public static void EnableSchedulingFromForeignThread(KernelContext context, ulong scheduledCoresMask)
+ {
+ RescheduleOtherCores(context, scheduledCoresMask);
+ }
+
+ private void RescheduleCurrentCore()
+ {
+ if (_state.NeedsScheduling)
+ {
+ Schedule();
+ }
+ }
+
+ private void RescheduleOtherCores(ulong scheduledCoresMask)
+ {
+ RescheduleOtherCores(_context, scheduledCoresMask & ~(1UL << _coreId));
+ }
+
+ private static void RescheduleOtherCores(KernelContext context, ulong scheduledCoresMask)
+ {
+ while (scheduledCoresMask != 0)
+ {
+ int coreToSignal = BitOperations.TrailingZeroCount(scheduledCoresMask);
+
+ KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
+
+ // Request the thread running on that core to stop and reschedule, if we have one.
+ if (threadToSignal != context.Schedulers[coreToSignal]._idleThread)
+ {
+ threadToSignal.Context.RequestInterrupt();
+ }
+
+ // If the core is idle, ensure that the idle thread is awaken.
+ context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
+
+ scheduledCoresMask &= ~(1UL << coreToSignal);
+ }
+ }
+
+ private void IdleThreadLoop()
+ {
+ while (_context.Running)
+ {
+ _state.NeedsScheduling = false;
+ Thread.MemoryBarrier();
+ KThread nextThread = PickNextThread(_state.SelectedThread);
+
+ if (_idleThread != nextThread)
+ {
+ _idleThread.SchedulerWaitEvent.Reset();
+ WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
+ }
+
+ _idleInterruptEvent.WaitOne();
+ }
+
+ lock (_idleInterruptEventLock)
+ {
+ _idleInterruptEvent.Dispose();
+ _idleInterruptEvent = null;
+ }
+ }
+
+ public void Schedule()
+ {
+ _state.NeedsScheduling = false;
+ Thread.MemoryBarrier();
+ KThread currentThread = KernelStatic.GetCurrentThread();
+ KThread selectedThread = _state.SelectedThread;
+
+ // If the thread is already scheduled and running on the core, we have nothing to do.
+ if (currentThread == selectedThread)
+ {
+ return;
+ }
+
+ currentThread.SchedulerWaitEvent.Reset();
+ currentThread.ThreadContext.Unlock();
+
+ // Wake all the threads that might be waiting until this thread context is unlocked.
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ _context.Schedulers[core]._idleInterruptEvent.Set();
+ }
+
+ KThread nextThread = PickNextThread(selectedThread);
+
+ if (currentThread.Context.Running)
+ {
+ // Wait until this thread is scheduled again, and allow the next thread to run.
+ WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
+ }
+ else
+ {
+ // Allow the next thread to run.
+ nextThread.SchedulerWaitEvent.Set();
+
+ // We don't need to wait since the thread is exiting, however we need to
+ // make sure this thread will never call the scheduler again, since it is
+ // no longer assigned to a core.
+ currentThread.MakeUnschedulable();
+
+ // Just to be sure, set the core to a invalid value.
+ // This will trigger a exception if it attempts to call schedule again,
+ // rather than leaving the scheduler in a invalid state.
+ currentThread.CurrentCore = -1;
+ }
+ }
+
+ private KThread PickNextThread(KThread selectedThread)
+ {
+ while (true)
+ {
+ if (selectedThread != null)
+ {
+ // Try to run the selected thread.
+ // We need to acquire the context lock to be sure the thread is not
+ // already running on another core. If it is, then we return here
+ // and the caller should try again once there is something available for scheduling.
+ // The thread currently running on the core should have been requested to
+ // interrupt so this is not expected to take long.
+ // The idle thread must also be paused if we are scheduling a thread
+ // on the core, as the scheduled thread will handle the next switch.
+ if (selectedThread.ThreadContext.Lock())
+ {
+ SwitchTo(selectedThread);
+
+ if (!_state.NeedsScheduling)
+ {
+ return selectedThread;
+ }
+
+ selectedThread.ThreadContext.Unlock();
+ }
+ else
+ {
+ return _idleThread;
+ }
+ }
+ else
+ {
+ // The core is idle now, make sure that the idle thread can run
+ // and switch the core when a thread is available.
+ SwitchTo(null);
+ return _idleThread;
+ }
+
+ _state.NeedsScheduling = false;
+ Thread.MemoryBarrier();
+ selectedThread = _state.SelectedThread;
+ }
+ }
+
+ private void SwitchTo(KThread nextThread)
+ {
+ KProcess currentProcess = KernelStatic.GetCurrentProcess();
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ nextThread ??= _idleThread;
+
+ if (currentThread != nextThread)
+ {
+ long previousTicks = LastContextSwitchTime;
+ long currentTicks = PerformanceCounter.ElapsedTicks;
+ long ticksDelta = currentTicks - previousTicks;
+
+ currentThread.AddCpuTime(ticksDelta);
+
+ if (currentProcess != null)
+ {
+ currentProcess.AddCpuTime(ticksDelta);
+ }
+
+ LastContextSwitchTime = currentTicks;
+
+ if (currentProcess != null)
+ {
+ _previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
+ }
+ else if (currentThread == _idleThread)
+ {
+ _previousThread = null;
+ }
+ }
+
+ if (nextThread.CurrentCore != _coreId)
+ {
+ nextThread.CurrentCore = _coreId;
+ }
+
+ _currentThread = nextThread;
+ }
+
+ public static void PreemptionThreadLoop(KernelContext context)
+ {
+ while (context.Running)
+ {
+ context.CriticalSection.Enter();
+
+ for (int core = 0; core < CpuCoresCount; core++)
+ {
+ RotateScheduledQueue(context, core, PreemptionPriorities[core]);
+ }
+
+ context.CriticalSection.Leave();
+
+ Thread.Sleep(RoundRobinTimeQuantumMs);
+ }
+ }
+
+ private static void RotateScheduledQueue(KernelContext context, int core, int prio)
+ {
+ KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio);
+ KThread nextThread = null;
+
+ // Yield priority queue.
+ if (selectedThread != null)
+ {
+ nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
+ }
+
+ static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate< KThread> predicate)
+ {
+ foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+ {
+ int suggestedCore = suggested.ActiveCore;
+ if (suggestedCore >= 0)
+ {
+ KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
+
+ if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
+ {
+ continue;
+ }
+ }
+
+ // If the candidate was scheduled after the current thread, then it's not worth it.
+ if (nextThread == selectedThread ||
+ nextThread == null ||
+ nextThread.LastScheduledTime >= suggested.LastScheduledTime)
+ {
+ if (predicate(suggested))
+ {
+ return suggested;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // Select candidate threads that could run on this core.
+ // Only take into account threads that are not yet selected.
+ KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio);
+
+ if (dst != null)
+ {
+ context.PriorityQueue.TransferToCore(prio, core, dst);
+ }
+
+ // If the priority of the currently selected thread is lower or same as the preemption priority,
+ // then try to migrate a thread with lower priority.
+ KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
+
+ if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
+ {
+ dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority);
+
+ if (dst != null)
+ {
+ context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
+ }
+ }
+
+ context.ThreadReselectionRequested = true;
+ }
+
+ public static void Yield(KernelContext context)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (!currentThread.IsSchedulable)
+ {
+ return;
+ }
+
+ context.CriticalSection.Enter();
+
+ if (currentThread.SchedFlags != ThreadSchedState.Running)
+ {
+ context.CriticalSection.Leave();
+ return;
+ }
+
+ KThread nextThread = context.PriorityQueue.Reschedule(currentThread.DynamicPriority, currentThread.ActiveCore, currentThread);
+
+ if (nextThread != currentThread)
+ {
+ context.ThreadReselectionRequested = true;
+ }
+
+ context.CriticalSection.Leave();
+ }
+
+ public static void YieldWithLoadBalancing(KernelContext context)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (!currentThread.IsSchedulable)
+ {
+ return;
+ }
+
+ context.CriticalSection.Enter();
+
+ if (currentThread.SchedFlags != ThreadSchedState.Running)
+ {
+ context.CriticalSection.Leave();
+ return;
+ }
+
+ int prio = currentThread.DynamicPriority;
+ int core = currentThread.ActiveCore;
+
+ // Move current thread to the end of the queue.
+ KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
+
+ static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority)
+ {
+ foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+ {
+ int suggestedCore = suggested.ActiveCore;
+ if (suggestedCore >= 0)
+ {
+ KThread selectedSuggestedCore = context.Schedulers[suggestedCore]._state.SelectedThread;
+
+ if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
+ {
+ continue;
+ }
+ }
+
+ // If the candidate was scheduled after the current thread, then it's not worth it,
+ // unless the priority is higher than the current one.
+ if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
+ suggested.DynamicPriority < nextThread.DynamicPriority)
+ {
+ if (suggested.DynamicPriority <= lessThanOrEqualPriority)
+ {
+ return suggested;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio);
+
+ if (dst != null)
+ {
+ context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
+
+ context.ThreadReselectionRequested = true;
+ }
+ else if (currentThread != nextThread)
+ {
+ context.ThreadReselectionRequested = true;
+ }
+
+ context.CriticalSection.Leave();
+ }
+
+ public static void YieldToAnyThread(KernelContext context)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (!currentThread.IsSchedulable)
+ {
+ return;
+ }
+
+ context.CriticalSection.Enter();
+
+ if (currentThread.SchedFlags != ThreadSchedState.Running)
+ {
+ context.CriticalSection.Leave();
+ return;
+ }
+
+ int core = currentThread.ActiveCore;
+
+ context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
+
+ if (!context.PriorityQueue.HasScheduledThreads(core))
+ {
+ KThread selectedThread = null;
+
+ foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+ {
+ int suggestedCore = suggested.ActiveCore;
+
+ if (suggestedCore < 0)
+ {
+ continue;
+ }
+
+ KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
+
+ if (firstCandidate == suggested)
+ {
+ continue;
+ }
+
+ if (firstCandidate == null || firstCandidate.DynamicPriority >= 2)
+ {
+ context.PriorityQueue.TransferToCore(suggested.DynamicPriority, core, suggested);
+ }
+
+ selectedThread = suggested;
+ break;
+ }
+
+ if (currentThread != selectedThread)
+ {
+ context.ThreadReselectionRequested = true;
+ }
+ }
+ else
+ {
+ context.ThreadReselectionRequested = true;
+ }
+
+ context.CriticalSection.Leave();
+ }
+
+ public void Dispose()
+ {
+ // Ensure that the idle thread is not blocked and can exit.
+ lock (_idleInterruptEventLock)
+ {
+ if (_idleInterruptEvent != null)
+ {
+ _idleInterruptEvent.Set();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
new file mode 100644
index 00000000..9c196810
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
@@ -0,0 +1,142 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KSynchronization
+ {
+ private KernelContext _context;
+
+ public KSynchronization(KernelContext context)
+ {
+ _context = context;
+ }
+
+ public Result WaitFor(Span<KSynchronizationObject> syncObjs, long timeout, out int handleIndex)
+ {
+ handleIndex = 0;
+
+ Result result = KernelResult.TimedOut;
+
+ _context.CriticalSection.Enter();
+
+ // Check if objects are already signaled before waiting.
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ if (!syncObjs[index].IsSignaled())
+ {
+ continue;
+ }
+
+ handleIndex = index;
+
+ _context.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ if (timeout == 0)
+ {
+ _context.CriticalSection.Leave();
+
+ return result;
+ }
+
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.TerminationRequested)
+ {
+ result = KernelResult.ThreadTerminating;
+ }
+ else if (currentThread.SyncCancelled)
+ {
+ currentThread.SyncCancelled = false;
+
+ result = KernelResult.Cancelled;
+ }
+ else
+ {
+ LinkedListNode<KThread>[] syncNodesArray = ArrayPool<LinkedListNode<KThread>>.Shared.Rent(syncObjs.Length);
+
+ Span<LinkedListNode<KThread>> syncNodes = syncNodesArray.AsSpan(0, syncObjs.Length);
+
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread);
+ }
+
+ currentThread.WaitingSync = true;
+ currentThread.SignaledObj = null;
+ currentThread.ObjSyncResult = result;
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
+ }
+
+ _context.CriticalSection.Leave();
+
+ currentThread.WaitingSync = false;
+
+ if (timeout > 0)
+ {
+ _context.TimeManager.UnscheduleFutureInvocation(currentThread);
+ }
+
+ _context.CriticalSection.Enter();
+
+ result = currentThread.ObjSyncResult;
+
+ handleIndex = -1;
+
+ for (int index = 0; index < syncObjs.Length; index++)
+ {
+ syncObjs[index].RemoveWaitingThread(syncNodes[index]);
+
+ if (syncObjs[index] == currentThread.SignaledObj)
+ {
+ handleIndex = index;
+ }
+ }
+
+ ArrayPool<LinkedListNode<KThread>>.Shared.Return(syncNodesArray);
+ }
+
+ _context.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public void SignalObject(KSynchronizationObject syncObj)
+ {
+ _context.CriticalSection.Enter();
+
+ if (syncObj.IsSignaled())
+ {
+ LinkedListNode<KThread> node = syncObj.WaitingThreads.First;
+
+ while (node != null)
+ {
+ KThread thread = node.Value;
+
+ if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+ {
+ thread.SignaledObj = syncObj;
+ thread.ObjSyncResult = Result.Success;
+
+ thread.Reschedule(ThreadSchedState.Running);
+ }
+
+ node = node.Next;
+ }
+ }
+
+ _context.CriticalSection.Leave();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
new file mode 100644
index 00000000..63396468
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -0,0 +1,1438 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KThread : KSynchronizationObject, IKFutureSchedulerObject
+ {
+ private const int TlsUserDisableCountOffset = 0x100;
+ private const int TlsUserInterruptFlagOffset = 0x102;
+
+ public const int MaxWaitSyncObjects = 64;
+
+ private ManualResetEvent _schedulerWaitEvent;
+
+ public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent;
+
+ public Thread HostThread { get; private set; }
+
+ public IExecutionContext Context { get; private set; }
+
+ public KThreadContext ThreadContext { get; private set; }
+
+ public int DynamicPriority { get; set; }
+ public ulong AffinityMask { get; set; }
+
+ public ulong ThreadUid { get; private set; }
+
+ private long _totalTimeRunning;
+
+ public long TotalTimeRunning => _totalTimeRunning;
+
+ public KSynchronizationObject SignaledObj { get; set; }
+
+ public ulong CondVarAddress { get; set; }
+
+ private ulong _entrypoint;
+ private ThreadStart _customThreadStart;
+ private bool _forcedUnschedulable;
+
+ public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable;
+
+ public ulong MutexAddress { get; set; }
+ public int KernelWaitersCount { get; private set; }
+
+ public KProcess Owner { get; private set; }
+
+ private ulong _tlsAddress;
+
+ public ulong TlsAddress => _tlsAddress;
+
+ public KSynchronizationObject[] WaitSyncObjects { get; }
+ public int[] WaitSyncHandles { get; }
+
+ public long LastScheduledTime { get; set; }
+
+ public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; }
+
+ public LinkedList<KThread> Withholder { get; set; }
+ public LinkedListNode<KThread> WithholderNode { get; set; }
+
+ public LinkedListNode<KThread> ProcessListNode { get; set; }
+
+ private LinkedList<KThread> _mutexWaiters;
+ private LinkedListNode<KThread> _mutexWaiterNode;
+
+ private LinkedList<KThread> _pinnedWaiters;
+
+ public KThread MutexOwner { get; private set; }
+
+ public int ThreadHandleForUserMutex { get; set; }
+
+ private ThreadSchedState _forcePauseFlags;
+ private ThreadSchedState _forcePausePermissionFlags;
+
+ public Result ObjSyncResult { get; set; }
+
+ public int BasePriority { get; set; }
+ public int PreferredCore { get; set; }
+
+ public int CurrentCore { get; set; }
+ public int ActiveCore { get; set; }
+
+ public bool IsPinned { get; private set; }
+
+ private ulong _originalAffinityMask;
+ private int _originalPreferredCore;
+ private int _originalBasePriority;
+ private int _coreMigrationDisableCount;
+
+ public ThreadSchedState SchedFlags { get; private set; }
+
+ private int _shallBeTerminated;
+
+ private bool ShallBeTerminated => _shallBeTerminated != 0;
+
+ public bool TerminationRequested => ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending;
+
+ public bool SyncCancelled { get; set; }
+ public bool WaitingSync { get; set; }
+
+ private int _hasExited;
+ private bool _hasBeenInitialized;
+ private bool _hasBeenReleased;
+
+ public bool WaitingInArbitration { get; set; }
+
+ private object _activityOperationLock;
+
+ public KThread(KernelContext context) : base(context)
+ {
+ WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
+ WaitSyncHandles = new int[MaxWaitSyncObjects];
+
+ SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
+
+ _mutexWaiters = new LinkedList<KThread>();
+ _pinnedWaiters = new LinkedList<KThread>();
+
+ _activityOperationLock = new object();
+ }
+
+ public Result Initialize(
+ ulong entrypoint,
+ ulong argsPtr,
+ ulong stackTop,
+ int priority,
+ int cpuCore,
+ KProcess owner,
+ ThreadType type,
+ ThreadStart customThreadStart = null)
+ {
+ if ((uint)type > 3)
+ {
+ throw new ArgumentException($"Invalid thread type \"{type}\".");
+ }
+
+ PreferredCore = cpuCore;
+ AffinityMask |= 1UL << cpuCore;
+
+ SchedFlags = type == ThreadType.Dummy
+ ? ThreadSchedState.Running
+ : ThreadSchedState.None;
+
+ ActiveCore = cpuCore;
+ ObjSyncResult = KernelResult.ThreadNotStarted;
+ DynamicPriority = priority;
+ BasePriority = priority;
+ CurrentCore = cpuCore;
+ IsPinned = false;
+
+ _entrypoint = entrypoint;
+ _customThreadStart = customThreadStart;
+
+ if (type == ThreadType.User)
+ {
+ if (owner.AllocateThreadLocalStorage(out _tlsAddress) != Result.Success)
+ {
+ return KernelResult.OutOfMemory;
+ }
+
+ MemoryHelper.FillWithZeros(owner.CpuMemory, _tlsAddress, KTlsPageInfo.TlsEntrySize);
+ }
+
+ bool is64Bits;
+
+ if (owner != null)
+ {
+ Owner = owner;
+
+ owner.IncrementReferenceCount();
+ owner.IncrementThreadCount();
+
+ is64Bits = owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit);
+ }
+ else
+ {
+ is64Bits = true;
+ }
+
+ HostThread = new Thread(ThreadStart);
+
+ Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext();
+
+ ThreadContext = new KThreadContext(Context);
+
+ Context.IsAarch32 = !is64Bits;
+
+ Context.SetX(0, argsPtr);
+
+ if (is64Bits)
+ {
+ Context.SetX(18, KSystemControl.GenerateRandom() | 1);
+ Context.SetX(31, stackTop);
+ }
+ else
+ {
+ Context.SetX(13, (uint)stackTop);
+ }
+
+ Context.TpidrroEl0 = (long)_tlsAddress;
+
+ ThreadUid = KernelContext.NewThreadUid();
+
+ HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
+
+ _hasBeenInitialized = true;
+
+ _forcePausePermissionFlags = ThreadSchedState.ForcePauseMask;
+
+ if (owner != null)
+ {
+ owner.AddThread(this);
+
+ if (owner.IsPaused)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return Result.Success;
+ }
+
+ _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag;
+
+ CombineForcePauseFlags();
+
+ KernelContext.CriticalSection.Leave();
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result Start()
+ {
+ if (!KernelContext.KernelInitialized)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (!TerminationRequested)
+ {
+ _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag;
+
+ CombineForcePauseFlags();
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ Result result = KernelResult.ThreadTerminating;
+
+ KernelContext.CriticalSection.Enter();
+
+ if (!ShallBeTerminated)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ while (SchedFlags != ThreadSchedState.TerminationPending && (currentThread == null || !currentThread.TerminationRequested))
+ {
+ if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None)
+ {
+ result = KernelResult.InvalidState;
+ break;
+ }
+
+ if (currentThread == null || currentThread._forcePauseFlags == ThreadSchedState.None)
+ {
+ if (Owner != null && _forcePauseFlags != ThreadSchedState.None)
+ {
+ CombineForcePauseFlags();
+ }
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ StartHostThread();
+
+ result = Result.Success;
+ break;
+ }
+ else
+ {
+ currentThread.CombineForcePauseFlags();
+
+ KernelContext.CriticalSection.Leave();
+ KernelContext.CriticalSection.Enter();
+
+ if (currentThread.ShallBeTerminated)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return result;
+ }
+
+ public ThreadSchedState PrepareForTermination()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
+ {
+ Owner.UnpinThread(this);
+ }
+
+ ThreadSchedState result;
+
+ if (Interlocked.Exchange(ref _shallBeTerminated, 1) == 0)
+ {
+ if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None)
+ {
+ SchedFlags = ThreadSchedState.TerminationPending;
+ }
+ else
+ {
+ if (_forcePauseFlags != ThreadSchedState.None)
+ {
+ _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag;
+
+ ThreadSchedState oldSchedFlags = SchedFlags;
+
+ SchedFlags &= ThreadSchedState.LowMask;
+
+ AdjustScheduling(oldSchedFlags);
+ }
+
+ if (BasePriority >= 0x10)
+ {
+ SetPriority(0xF);
+ }
+
+ if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running)
+ {
+ // TODO: GIC distributor stuffs (sgir changes ect)
+ Context.RequestInterrupt();
+ }
+
+ SignaledObj = null;
+ ObjSyncResult = KernelResult.ThreadTerminating;
+
+ ReleaseAndResume();
+ }
+ }
+
+ result = SchedFlags;
+
+ KernelContext.CriticalSection.Leave();
+
+ return result & ThreadSchedState.LowMask;
+ }
+
+ public void Terminate()
+ {
+ ThreadSchedState state = PrepareForTermination();
+
+ if (state != ThreadSchedState.TerminationPending)
+ {
+ KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
+ }
+ }
+
+ public void HandlePostSyscall()
+ {
+ ThreadSchedState state;
+
+ do
+ {
+ if (TerminationRequested)
+ {
+ Exit();
+
+ // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here.
+ break;
+ }
+
+ KernelContext.CriticalSection.Enter();
+
+ if (TerminationRequested)
+ {
+ state = ThreadSchedState.TerminationPending;
+ }
+ else
+ {
+ if (_forcePauseFlags != ThreadSchedState.None)
+ {
+ CombineForcePauseFlags();
+ }
+
+ state = ThreadSchedState.Running;
+ }
+
+ KernelContext.CriticalSection.Leave();
+ } while (state == ThreadSchedState.TerminationPending);
+ }
+
+ public void Exit()
+ {
+ // TODO: Debug event.
+
+ if (Owner != null)
+ {
+ Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1);
+
+ _hasBeenReleased = true;
+ }
+
+ KernelContext.CriticalSection.Enter();
+
+ _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
+ _forcePausePermissionFlags = 0;
+
+ bool decRef = ExitImpl();
+
+ Context.StopRunning();
+
+ KernelContext.CriticalSection.Leave();
+
+ if (decRef)
+ {
+ DecrementReferenceCount();
+ }
+ }
+
+ private bool ExitImpl()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ SetNewSchedFlags(ThreadSchedState.TerminationPending);
+
+ bool decRef = Interlocked.Exchange(ref _hasExited, 1) == 0;
+
+ Signal();
+
+ KernelContext.CriticalSection.Leave();
+
+ return decRef;
+ }
+
+ private int GetEffectiveRunningCore()
+ {
+ for (int coreNumber = 0; coreNumber < KScheduler.CpuCoresCount; coreNumber++)
+ {
+ if (KernelContext.Schedulers[coreNumber].CurrentThread == this)
+ {
+ return coreNumber;
+ }
+ }
+
+ return -1;
+ }
+
+ public Result Sleep(long timeout)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ SetNewSchedFlags(ThreadSchedState.Paused);
+
+ if (timeout > 0)
+ {
+ KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout);
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (timeout > 0)
+ {
+ KernelContext.TimeManager.UnscheduleFutureInvocation(this);
+ }
+
+ return Result.Success;
+ }
+
+ public void SetPriority(int priority)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (IsPinned)
+ {
+ _originalBasePriority = priority;
+ }
+ else
+ {
+ BasePriority = priority;
+ }
+
+ UpdatePriorityInheritance();
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public void Suspend(ThreadSchedState type)
+ {
+ _forcePauseFlags |= type;
+
+ CombineForcePauseFlags();
+ }
+
+ public void Resume(ThreadSchedState type)
+ {
+ ThreadSchedState oldForcePauseFlags = _forcePauseFlags;
+
+ _forcePauseFlags &= ~type;
+
+ if ((oldForcePauseFlags & ~type) == ThreadSchedState.None)
+ {
+ ThreadSchedState oldSchedFlags = SchedFlags;
+
+ SchedFlags &= ThreadSchedState.LowMask;
+
+ AdjustScheduling(oldSchedFlags);
+ }
+ }
+
+ public Result SetActivity(bool pause)
+ {
+ lock (_activityOperationLock)
+ {
+ Result result = Result.Success;
+
+ KernelContext.CriticalSection.Enter();
+
+ ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
+
+ if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ if (!TerminationRequested)
+ {
+ if (pause)
+ {
+ // Pause, the force pause flag should be clear (thread is NOT paused).
+ if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
+ {
+ Suspend(ThreadSchedState.ThreadPauseFlag);
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+ else
+ {
+ // Unpause, the force pause flag should be set (thread is paused).
+ if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
+ {
+ Resume(ThreadSchedState.ThreadPauseFlag);
+ }
+ else
+ {
+ result = KernelResult.InvalidState;
+ }
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ if (result == Result.Success && pause)
+ {
+ bool isThreadRunning = true;
+
+ while (isThreadRunning)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ break;
+ }
+
+ isThreadRunning = false;
+
+ if (IsPinned)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ result = KernelResult.ThreadTerminating;
+
+ break;
+ }
+
+ _pinnedWaiters.AddLast(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+ }
+ else
+ {
+ isThreadRunning = GetEffectiveRunningCore() >= 0;
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+ }
+
+ return result;
+ }
+ }
+
+ public Result GetThreadContext3(out ThreadContext context)
+ {
+ context = default;
+
+ lock (_activityOperationLock)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ if (!TerminationRequested)
+ {
+ context = GetCurrentContext();
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ return Result.Success;
+ }
+
+ private static uint GetPsr(IExecutionContext context)
+ {
+ return context.Pstate & 0xFF0FFE20;
+ }
+
+ private ThreadContext GetCurrentContext()
+ {
+ const int MaxRegistersAArch32 = 15;
+ const int MaxFpuRegistersAArch32 = 16;
+
+ ThreadContext context = new ThreadContext();
+
+ if (Owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit))
+ {
+ for (int i = 0; i < context.Registers.Length; i++)
+ {
+ context.Registers[i] = Context.GetX(i);
+ }
+
+ for (int i = 0; i < context.FpuRegisters.Length; i++)
+ {
+ context.FpuRegisters[i] = Context.GetV(i);
+ }
+
+ context.Fp = Context.GetX(29);
+ context.Lr = Context.GetX(30);
+ context.Sp = Context.GetX(31);
+ context.Pc = Context.Pc;
+ context.Pstate = GetPsr(Context);
+ context.Tpidr = (ulong)Context.TpidrroEl0;
+ }
+ else
+ {
+ for (int i = 0; i < MaxRegistersAArch32; i++)
+ {
+ context.Registers[i] = (uint)Context.GetX(i);
+ }
+
+ for (int i = 0; i < MaxFpuRegistersAArch32; i++)
+ {
+ context.FpuRegisters[i] = Context.GetV(i);
+ }
+
+ context.Pc = (uint)Context.Pc;
+ context.Pstate = GetPsr(Context);
+ context.Tpidr = (uint)Context.TpidrroEl0;
+ }
+
+ context.Fpcr = (uint)Context.Fpcr;
+ context.Fpsr = (uint)Context.Fpsr;
+
+ return context;
+ }
+
+ public void CancelSynchronization()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync)
+ {
+ SyncCancelled = true;
+ }
+ else if (Withholder != null)
+ {
+ Withholder.Remove(WithholderNode);
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ Withholder = null;
+
+ SyncCancelled = true;
+ }
+ else
+ {
+ SignaledObj = null;
+ ObjSyncResult = KernelResult.Cancelled;
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ SyncCancelled = false;
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public Result SetCoreAndAffinityMask(int newCore, ulong newAffinityMask)
+ {
+ lock (_activityOperationLock)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ bool isCoreMigrationDisabled = _coreMigrationDisableCount != 0;
+
+ // The value -3 is "do not change the preferred core".
+ if (newCore == -3)
+ {
+ newCore = isCoreMigrationDisabled ? _originalPreferredCore : PreferredCore;
+
+ if ((newAffinityMask & (1UL << newCore)) == 0)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidCombination;
+ }
+ }
+
+ if (isCoreMigrationDisabled)
+ {
+ _originalPreferredCore = newCore;
+ _originalAffinityMask = newAffinityMask;
+ }
+ else
+ {
+ ulong oldAffinityMask = AffinityMask;
+
+ PreferredCore = newCore;
+ AffinityMask = newAffinityMask;
+
+ if (oldAffinityMask != newAffinityMask)
+ {
+ int oldCore = ActiveCore;
+
+ if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0)
+ {
+ if (PreferredCore < 0)
+ {
+ ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount(AffinityMask);
+ }
+ else
+ {
+ ActiveCore = PreferredCore;
+ }
+ }
+
+ AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore);
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ bool targetThreadPinned = true;
+
+ while (targetThreadPinned)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ break;
+ }
+
+ targetThreadPinned = false;
+
+ int coreNumber = GetEffectiveRunningCore();
+ bool isPinnedThreadCurrentlyRunning = coreNumber >= 0;
+
+ if (isPinnedThreadCurrentlyRunning && ((1UL << coreNumber) & AffinityMask) == 0)
+ {
+ if (IsPinned)
+ {
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (currentThread.TerminationRequested)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.ThreadTerminating;
+ }
+
+ _pinnedWaiters.AddLast(currentThread);
+
+ currentThread.Reschedule(ThreadSchedState.Paused);
+ }
+ else
+ {
+ targetThreadPinned = true;
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ return Result.Success;
+ }
+ }
+
+ private void CombineForcePauseFlags()
+ {
+ ThreadSchedState oldFlags = SchedFlags;
+ ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask;
+
+ SchedFlags = lowNibble | (_forcePauseFlags & _forcePausePermissionFlags);
+
+ AdjustScheduling(oldFlags);
+ }
+
+ private void SetNewSchedFlags(ThreadSchedState newFlags)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ ThreadSchedState oldFlags = SchedFlags;
+
+ SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags;
+
+ if ((oldFlags & ThreadSchedState.LowMask) != newFlags)
+ {
+ AdjustScheduling(oldFlags);
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public void ReleaseAndResume()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+ {
+ if (Withholder != null)
+ {
+ Withholder.Remove(WithholderNode);
+
+ SetNewSchedFlags(ThreadSchedState.Running);
+
+ Withholder = null;
+ }
+ else
+ {
+ SetNewSchedFlags(ThreadSchedState.Running);
+ }
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public void Reschedule(ThreadSchedState newFlags)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ ThreadSchedState oldFlags = SchedFlags;
+
+ SchedFlags = (oldFlags & ThreadSchedState.HighMask) |
+ (newFlags & ThreadSchedState.LowMask);
+
+ AdjustScheduling(oldFlags);
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public void AddMutexWaiter(KThread requester)
+ {
+ AddToMutexWaitersList(requester);
+
+ requester.MutexOwner = this;
+
+ UpdatePriorityInheritance();
+ }
+
+ public void RemoveMutexWaiter(KThread thread)
+ {
+ if (thread._mutexWaiterNode?.List != null)
+ {
+ _mutexWaiters.Remove(thread._mutexWaiterNode);
+ }
+
+ thread.MutexOwner = null;
+
+ UpdatePriorityInheritance();
+ }
+
+ public KThread RelinquishMutex(ulong mutexAddress, out int count)
+ {
+ count = 0;
+
+ if (_mutexWaiters.First == null)
+ {
+ return null;
+ }
+
+ KThread newMutexOwner = null;
+
+ LinkedListNode<KThread> currentNode = _mutexWaiters.First;
+
+ do
+ {
+ // Skip all threads that are not waiting for this mutex.
+ while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress)
+ {
+ currentNode = currentNode.Next;
+ }
+
+ if (currentNode == null)
+ {
+ break;
+ }
+
+ LinkedListNode<KThread> nextNode = currentNode.Next;
+
+ _mutexWaiters.Remove(currentNode);
+
+ currentNode.Value.MutexOwner = newMutexOwner;
+
+ if (newMutexOwner != null)
+ {
+ // New owner was already selected, re-insert on new owner list.
+ newMutexOwner.AddToMutexWaitersList(currentNode.Value);
+ }
+ else
+ {
+ // New owner not selected yet, use current thread.
+ newMutexOwner = currentNode.Value;
+ }
+
+ count++;
+
+ currentNode = nextNode;
+ }
+ while (currentNode != null);
+
+ if (newMutexOwner != null)
+ {
+ UpdatePriorityInheritance();
+
+ newMutexOwner.UpdatePriorityInheritance();
+ }
+
+ return newMutexOwner;
+ }
+
+ private void UpdatePriorityInheritance()
+ {
+ // If any of the threads waiting for the mutex has
+ // higher priority than the current thread, then
+ // the current thread inherits that priority.
+ int highestPriority = BasePriority;
+
+ if (_mutexWaiters.First != null)
+ {
+ int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority;
+
+ if (waitingDynamicPriority < highestPriority)
+ {
+ highestPriority = waitingDynamicPriority;
+ }
+ }
+
+ if (highestPriority != DynamicPriority)
+ {
+ int oldPriority = DynamicPriority;
+
+ DynamicPriority = highestPriority;
+
+ AdjustSchedulingForNewPriority(oldPriority);
+
+ if (MutexOwner != null)
+ {
+ // Remove and re-insert to ensure proper sorting based on new priority.
+ MutexOwner._mutexWaiters.Remove(_mutexWaiterNode);
+
+ MutexOwner.AddToMutexWaitersList(this);
+
+ MutexOwner.UpdatePriorityInheritance();
+ }
+ }
+ }
+
+ private void AddToMutexWaitersList(KThread thread)
+ {
+ LinkedListNode<KThread> nextPrio = _mutexWaiters.First;
+
+ int currentPriority = thread.DynamicPriority;
+
+ while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority)
+ {
+ nextPrio = nextPrio.Next;
+ }
+
+ if (nextPrio != null)
+ {
+ thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread);
+ }
+ else
+ {
+ thread._mutexWaiterNode = _mutexWaiters.AddLast(thread);
+ }
+ }
+
+ private void AdjustScheduling(ThreadSchedState oldFlags)
+ {
+ if (oldFlags == SchedFlags)
+ {
+ return;
+ }
+
+ if (!IsSchedulable)
+ {
+ if (!_forcedUnschedulable)
+ {
+ // Ensure our thread is running and we have an event.
+ StartHostThread();
+
+ // If the thread is not schedulable, we want to just run or pause
+ // it directly as we don't care about priority or the core it is
+ // running on in this case.
+ if (SchedFlags == ThreadSchedState.Running)
+ {
+ _schedulerWaitEvent.Set();
+ }
+ else
+ {
+ _schedulerWaitEvent.Reset();
+ }
+ }
+
+ return;
+ }
+
+ if (oldFlags == ThreadSchedState.Running)
+ {
+ // Was running, now it's stopped.
+ if (ActiveCore >= 0)
+ {
+ KernelContext.PriorityQueue.Unschedule(DynamicPriority, ActiveCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this);
+ }
+ }
+ }
+ else if (SchedFlags == ThreadSchedState.Running)
+ {
+ // Was stopped, now it's running.
+ if (ActiveCore >= 0)
+ {
+ KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+
+ private void AdjustSchedulingForNewPriority(int oldPriority)
+ {
+ if (SchedFlags != ThreadSchedState.Running || !IsSchedulable)
+ {
+ return;
+ }
+
+ // Remove thread from the old priority queues.
+ if (ActiveCore >= 0)
+ {
+ KernelContext.PriorityQueue.Unschedule(oldPriority, ActiveCore, this);
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ KernelContext.PriorityQueue.Unsuggest(oldPriority, core, this);
+ }
+ }
+
+ // Add thread to the new priority queues.
+ KThread currentThread = KernelStatic.GetCurrentThread();
+
+ if (ActiveCore >= 0)
+ {
+ if (currentThread == this)
+ {
+ KernelContext.PriorityQueue.SchedulePrepend(DynamicPriority, ActiveCore, this);
+ }
+ else
+ {
+ KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this);
+ }
+ }
+
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
+ {
+ KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
+ }
+ }
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+
+ private void AdjustSchedulingForNewAffinity(ulong oldAffinityMask, int oldCore)
+ {
+ if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount || !IsSchedulable)
+ {
+ return;
+ }
+
+ // Remove thread from the old priority queues.
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (((oldAffinityMask >> core) & 1) != 0)
+ {
+ if (core == oldCore)
+ {
+ KernelContext.PriorityQueue.Unschedule(DynamicPriority, core, this);
+ }
+ else
+ {
+ KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ // Add thread to the new priority queues.
+ for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+ {
+ if (((AffinityMask >> core) & 1) != 0)
+ {
+ if (core == ActiveCore)
+ {
+ KernelContext.PriorityQueue.Schedule(DynamicPriority, core, this);
+ }
+ else
+ {
+ KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
+ }
+ }
+ }
+
+ KernelContext.ThreadReselectionRequested = true;
+ }
+
+ public void SetEntryArguments(long argsPtr, int threadHandle)
+ {
+ Context.SetX(0, (ulong)argsPtr);
+ Context.SetX(1, (ulong)threadHandle);
+ }
+
+ public void TimeUp()
+ {
+ ReleaseAndResume();
+ }
+
+ public string GetGuestStackTrace()
+ {
+ return Owner.Debugger.GetGuestStackTrace(this);
+ }
+
+ public string GetGuestRegisterPrintout()
+ {
+ return Owner.Debugger.GetCpuRegisterPrintout(this);
+ }
+
+ public void PrintGuestStackTrace()
+ {
+ Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n");
+ }
+
+ public void PrintGuestRegisterPrintout()
+ {
+ Logger.Info?.Print(LogClass.Cpu, $"Guest CPU registers:\n{GetGuestRegisterPrintout()}\n");
+ }
+
+ public void AddCpuTime(long ticks)
+ {
+ Interlocked.Add(ref _totalTimeRunning, ticks);
+ }
+
+ public void StartHostThread()
+ {
+ if (_schedulerWaitEvent == null)
+ {
+ var schedulerWaitEvent = new ManualResetEvent(false);
+
+ if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
+ {
+ HostThread.Start();
+ }
+ else
+ {
+ schedulerWaitEvent.Dispose();
+ }
+ }
+ }
+
+ private void ThreadStart()
+ {
+ _schedulerWaitEvent.WaitOne();
+ KernelStatic.SetKernelContext(KernelContext, this);
+
+ if (_customThreadStart != null)
+ {
+ _customThreadStart();
+
+ // Ensure that anything trying to join the HLE thread is unblocked.
+ Exit();
+ HandlePostSyscall();
+ }
+ else
+ {
+ Owner.Context.Execute(Context, _entrypoint);
+ }
+
+ Context.Dispose();
+ _schedulerWaitEvent.Dispose();
+ }
+
+ public void MakeUnschedulable()
+ {
+ _forcedUnschedulable = true;
+ }
+
+ public override bool IsSignaled()
+ {
+ return _hasExited != 0;
+ }
+
+ protected override void Destroy()
+ {
+ if (_hasBeenInitialized)
+ {
+ FreeResources();
+
+ bool released = Owner != null || _hasBeenReleased;
+
+ if (Owner != null)
+ {
+ Owner.ResourceLimit?.Release(LimitableResource.Thread, 1, released ? 0 : 1);
+
+ Owner.DecrementReferenceCount();
+ }
+ else
+ {
+ KernelContext.ResourceLimit.Release(LimitableResource.Thread, 1, released ? 0 : 1);
+ }
+ }
+ }
+
+ private void FreeResources()
+ {
+ Owner?.RemoveThread(this);
+
+ if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != Result.Success)
+ {
+ throw new InvalidOperationException("Unexpected failure freeing thread local storage.");
+ }
+
+ KernelContext.CriticalSection.Enter();
+
+ // Wake up all threads that may be waiting for a mutex being held by this thread.
+ foreach (KThread thread in _mutexWaiters)
+ {
+ thread.MutexOwner = null;
+ thread._originalPreferredCore = 0;
+ thread.ObjSyncResult = KernelResult.InvalidState;
+
+ thread.ReleaseAndResume();
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ Owner?.DecrementThreadCountAndTerminateIfZero();
+ }
+
+ public void Pin()
+ {
+ IsPinned = true;
+ _coreMigrationDisableCount++;
+
+ int activeCore = ActiveCore;
+
+ _originalPreferredCore = PreferredCore;
+ _originalAffinityMask = AffinityMask;
+
+ ActiveCore = CurrentCore;
+ PreferredCore = CurrentCore;
+ AffinityMask = 1UL << CurrentCore;
+
+ if (activeCore != CurrentCore || _originalAffinityMask != AffinityMask)
+ {
+ AdjustSchedulingForNewAffinity(_originalAffinityMask, activeCore);
+ }
+
+ _originalBasePriority = BasePriority;
+ BasePriority = Math.Min(_originalBasePriority, BitOperations.TrailingZeroCount(Owner.Capabilities.AllowedThreadPriosMask) - 1);
+ UpdatePriorityInheritance();
+
+ // Disallows thread pausing
+ _forcePausePermissionFlags &= ~ThreadSchedState.ThreadPauseFlag;
+ CombineForcePauseFlags();
+
+ // TODO: Assign reduced SVC permissions
+ }
+
+ public void Unpin()
+ {
+ IsPinned = false;
+ _coreMigrationDisableCount--;
+
+ ulong affinityMask = AffinityMask;
+ int activeCore = ActiveCore;
+
+ PreferredCore = _originalPreferredCore;
+ AffinityMask = _originalAffinityMask;
+
+ if (AffinityMask != affinityMask)
+ {
+ if ((AffinityMask & 1UL << ActiveCore) != 0)
+ {
+ if (PreferredCore >= 0)
+ {
+ ActiveCore = PreferredCore;
+ }
+ else
+ {
+ ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask);
+ }
+
+ AdjustSchedulingForNewAffinity(affinityMask, activeCore);
+ }
+ }
+
+ BasePriority = _originalBasePriority;
+ UpdatePriorityInheritance();
+
+ if (!TerminationRequested)
+ {
+ // Allows thread pausing
+ _forcePausePermissionFlags |= ThreadSchedState.ThreadPauseFlag;
+ CombineForcePauseFlags();
+
+ // TODO: Restore SVC permissions
+ }
+
+ // Wake up waiters
+ foreach (KThread waiter in _pinnedWaiters)
+ {
+ waiter.ReleaseAndResume();
+ }
+
+ _pinnedWaiters.Clear();
+ }
+
+ public void SynchronizePreemptionState()
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (Owner != null && Owner.PinnedThreads[CurrentCore] == this)
+ {
+ ClearUserInterruptFlag();
+
+ Owner.UnpinThread(this);
+ }
+
+ KernelContext.CriticalSection.Leave();
+ }
+
+ public ushort GetUserDisableCount()
+ {
+ return Owner.CpuMemory.Read<ushort>(_tlsAddress + TlsUserDisableCountOffset);
+ }
+
+ public void SetUserInterruptFlag()
+ {
+ Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 1);
+ }
+
+ public void ClearUserInterruptFlag()
+ {
+ Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs
new file mode 100644
index 00000000..e8ad53c2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Cpu;
+using Ryujinx.Horizon.Common;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KThreadContext : IThreadContext
+ {
+ private readonly IExecutionContext _context;
+
+ public bool Running => _context.Running;
+ public ulong TlsAddress => (ulong)_context.TpidrroEl0;
+
+ public ulong GetX(int index) => _context.GetX(index);
+
+ private int _locked;
+
+ public KThreadContext(IExecutionContext context)
+ {
+ _context = context;
+ }
+
+ public bool Lock()
+ {
+ return Interlocked.Exchange(ref _locked, 1) == 0;
+ }
+
+ public void Unlock()
+ {
+ Interlocked.Exchange(ref _locked, 0);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
new file mode 100644
index 00000000..b46122be
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
@@ -0,0 +1,25 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ class KWritableEvent : KAutoObject
+ {
+ private readonly KEvent _parent;
+
+ public KWritableEvent(KernelContext context, KEvent parent) : base(context)
+ {
+ _parent = parent;
+ }
+
+ public void Signal()
+ {
+ _parent.ReadableEvent.Signal();
+ }
+
+ public Result Clear()
+ {
+ return _parent.ReadableEvent.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
new file mode 100644
index 00000000..e72b719b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum SignalType
+ {
+ Signal = 0,
+ SignalAndIncrementIfEqual = 1,
+ SignalAndModifyIfEqual = 2
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
new file mode 100644
index 00000000..9577075c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ThreadSchedState : ushort
+ {
+ LowMask = 0xf,
+ HighMask = 0xfff0,
+ ForcePauseMask = 0x1f0,
+
+ ProcessPauseFlag = 1 << 4,
+ ThreadPauseFlag = 1 << 5,
+ ProcessDebugPauseFlag = 1 << 6,
+ BacktracePauseFlag = 1 << 7,
+ KernelInitPauseFlag = 1 << 8,
+
+ None = 0,
+ Paused = 1,
+ Running = 2,
+ TerminationPending = 3
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
new file mode 100644
index 00000000..0b44b57f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+ enum ThreadType
+ {
+ Dummy,
+ Kernel,
+ Kernel2,
+ User
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs b/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs
new file mode 100644
index 00000000..8fde5dd6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs
@@ -0,0 +1,120 @@
+using LibHac;
+using LibHac.Bcat;
+using LibHac.Common;
+using LibHac.Fs.Fsa;
+using LibHac.Fs.Shim;
+using LibHac.FsSrv.Impl;
+using LibHac.Loader;
+using LibHac.Ncm;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Arp;
+using System;
+using StorageId = LibHac.Ncm.StorageId;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class LibHacHorizonManager
+ {
+ private LibHac.Horizon Server { get; set; }
+
+ public HorizonClient RyujinxClient { get; private set; }
+ public HorizonClient ApplicationClient { get; private set; }
+ public HorizonClient AccountClient { get; private set; }
+ public HorizonClient AmClient { get; private set; }
+ public HorizonClient BcatClient { get; private set; }
+ public HorizonClient FsClient { get; private set; }
+ public HorizonClient NsClient { get; private set; }
+ public HorizonClient PmClient { get; private set; }
+ public HorizonClient SdbClient { get; private set; }
+
+ private SharedRef<LibHacIReader> _arpIReader;
+ internal LibHacIReader ArpIReader => _arpIReader.Get;
+
+ public LibHacHorizonManager()
+ {
+ InitializeServer();
+ }
+
+ private void InitializeServer()
+ {
+ Server = new LibHac.Horizon(new HorizonConfiguration());
+
+ RyujinxClient = Server.CreatePrivilegedHorizonClient();
+ }
+
+ public void InitializeArpServer()
+ {
+ _arpIReader.Reset(new LibHacIReader());
+ RyujinxClient.Sm.RegisterService(new LibHacArpServiceObject(ref _arpIReader), "arp:r").ThrowIfFailure();
+ }
+
+ public void InitializeBcatServer()
+ {
+ BcatClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Bcat, StorageId.BuiltInSystem), BcatFsPermissions);
+
+ _ = new BcatServer(BcatClient);
+ }
+
+ public void InitializeFsServer(VirtualFileSystem virtualFileSystem)
+ {
+ virtualFileSystem.InitializeFsServer(Server, out var fsClient);
+
+ FsClient = fsClient;
+ }
+
+ public void InitializeSystemClients()
+ {
+ PmClient = Server.CreatePrivilegedHorizonClient();
+ AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), AccountFsPermissions);
+ AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), AmFsPermissions);
+ NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), NsFsPermissions);
+ SdbClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Sdb, StorageId.BuiltInSystem), SdbFacData, SdbFacDescriptor);
+ }
+
+ public void InitializeApplicationClient(ProgramId programId, in Npdm npdm)
+ {
+ ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor);
+ }
+
+ private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData |
+ AccessControlBits.Bits.GameCard |
+ AccessControlBits.Bits.SaveDataMeta |
+ AccessControlBits.Bits.GetRightsId;
+
+ private static AccessControlBits.Bits AmFsPermissions => AccessControlBits.Bits.SaveDataManagement |
+ AccessControlBits.Bits.CreateSaveData |
+ AccessControlBits.Bits.SystemData;
+ private static AccessControlBits.Bits BcatFsPermissions => AccessControlBits.Bits.SystemSaveData;
+
+ private static AccessControlBits.Bits NsFsPermissions => AccessControlBits.Bits.ApplicationInfo |
+ AccessControlBits.Bits.SystemSaveData |
+ AccessControlBits.Bits.GameCard |
+ AccessControlBits.Bits.SaveDataManagement |
+ AccessControlBits.Bits.ContentManager |
+ AccessControlBits.Bits.ImageManager |
+ AccessControlBits.Bits.SystemSaveDataManagement |
+ AccessControlBits.Bits.SystemUpdate |
+ AccessControlBits.Bits.SdCard |
+ AccessControlBits.Bits.FormatSdCard |
+ AccessControlBits.Bits.GetRightsId |
+ AccessControlBits.Bits.RegisterProgramIndexMapInfo |
+ AccessControlBits.Bits.MoveCacheStorage;
+
+ // Sdb has save data access control info so we can't store just its access control bits
+ private static ReadOnlySpan<byte> SdbFacData => new byte[]
+ {
+ 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x03, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01
+ };
+
+ private static ReadOnlySpan<byte> SdbFacDescriptor => new byte[]
+ {
+ 0x01, 0x00, 0x02, 0x00, 0x08, 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, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs
new file mode 100644
index 00000000..16512541
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/ModLoader.cs
@@ -0,0 +1,761 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Loader;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.RomFs;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Mods;
+using Ryujinx.HLE.Loaders.Processes;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using Path = System.IO.Path;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class ModLoader
+ {
+ private const string RomfsDir = "romfs";
+ private const string ExefsDir = "exefs";
+ private const string CheatDir = "cheats";
+ private const string RomfsContainer = "romfs.bin";
+ private const string ExefsContainer = "exefs.nsp";
+ private const string StubExtension = ".stub";
+ private const string CheatExtension = ".txt";
+ private const string DefaultCheatName = "<default>";
+
+ private const string AmsContentsDir = "contents";
+ private const string AmsNsoPatchDir = "exefs_patches";
+ private const string AmsNroPatchDir = "nro_patches";
+ private const string AmsKipPatchDir = "kip_patches";
+
+ public readonly struct Mod<T> where T : FileSystemInfo
+ {
+ public readonly string Name;
+ public readonly T Path;
+
+ public Mod(string name, T path)
+ {
+ Name = name;
+ Path = path;
+ }
+ }
+
+ public struct Cheat
+ {
+ // Atmosphere identifies the executables with the first 8 bytes
+ // of the build id, which is equivalent to 16 hex digits.
+ public const int CheatIdSize = 16;
+
+ public readonly string Name;
+ public readonly FileInfo Path;
+ public readonly IEnumerable<String> Instructions;
+
+ public Cheat(string name, FileInfo path, IEnumerable<String> instructions)
+ {
+ Name = name;
+ Path = path;
+ Instructions = instructions;
+ }
+ }
+
+ // Title dependent mods
+ public class ModCache
+ {
+ public List<Mod<FileInfo>> RomfsContainers { get; }
+ public List<Mod<FileInfo>> ExefsContainers { get; }
+
+ public List<Mod<DirectoryInfo>> RomfsDirs { get; }
+ public List<Mod<DirectoryInfo>> ExefsDirs { get; }
+
+ public List<Cheat> Cheats { get; }
+
+ public ModCache()
+ {
+ RomfsContainers = new List<Mod<FileInfo>>();
+ ExefsContainers = new List<Mod<FileInfo>>();
+ RomfsDirs = new List<Mod<DirectoryInfo>>();
+ ExefsDirs = new List<Mod<DirectoryInfo>>();
+ Cheats = new List<Cheat>();
+ }
+ }
+
+ // Title independent mods
+ public class PatchCache
+ {
+ public List<Mod<DirectoryInfo>> NsoPatches { get; }
+ public List<Mod<DirectoryInfo>> NroPatches { get; }
+ public List<Mod<DirectoryInfo>> KipPatches { get; }
+
+ internal bool Initialized { get; set; }
+
+ public PatchCache()
+ {
+ NsoPatches = new List<Mod<DirectoryInfo>>();
+ NroPatches = new List<Mod<DirectoryInfo>>();
+ KipPatches = new List<Mod<DirectoryInfo>>();
+
+ Initialized = false;
+ }
+ }
+
+ public Dictionary<ulong, ModCache> AppMods; // key is TitleId
+ public PatchCache Patches;
+
+ private static readonly EnumerationOptions _dirEnumOptions;
+
+ static ModLoader()
+ {
+ _dirEnumOptions = new EnumerationOptions
+ {
+ MatchCasing = MatchCasing.CaseInsensitive,
+ MatchType = MatchType.Simple,
+ RecurseSubdirectories = false,
+ ReturnSpecialDirectories = false
+ };
+ }
+
+ public ModLoader()
+ {
+ AppMods = new Dictionary<ulong, ModCache>();
+ Patches = new PatchCache();
+ }
+
+ public void Clear()
+ {
+ AppMods.Clear();
+ Patches = new PatchCache();
+ }
+
+ private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
+
+ public string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath());
+ public string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath());
+
+ private string EnsureBaseDirStructure(string modsBasePath)
+ {
+ var modsDir = new DirectoryInfo(modsBasePath);
+
+ modsDir.CreateSubdirectory(AmsContentsDir);
+ modsDir.CreateSubdirectory(AmsNsoPatchDir);
+ modsDir.CreateSubdirectory(AmsNroPatchDir);
+ // modsDir.CreateSubdirectory(AmsKipPatchDir); // uncomment when KIPs are supported
+
+ return modsDir.FullName;
+ }
+
+ private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId)
+ => contentsDir.EnumerateDirectories($"{titleId}*", _dirEnumOptions).FirstOrDefault();
+
+ public string GetTitleDir(string modsBasePath, string titleId)
+ {
+ var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir));
+ var titleModsPath = FindTitleDir(contentsDir, titleId);
+
+ if (titleModsPath == null)
+ {
+ Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Title {titleId.ToUpper()}");
+ titleModsPath = contentsDir.CreateSubdirectory(titleId);
+ }
+
+ return titleModsPath.FullName;
+ }
+
+ // Static Query Methods
+ public static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir)
+ {
+ if (cache.Initialized || !patchDir.Exists) return;
+
+ var patches = cache.KipPatches;
+ string type = null;
+
+ if (StrEquals(AmsNsoPatchDir, patchDir.Name)) { patches = cache.NsoPatches; type = "NSO"; }
+ else if (StrEquals(AmsNroPatchDir, patchDir.Name)) { patches = cache.NroPatches; type = "NRO"; }
+ else if (StrEquals(AmsKipPatchDir, patchDir.Name)) { patches = cache.KipPatches; type = "KIP"; }
+ else return;
+
+ foreach (var modDir in patchDir.EnumerateDirectories())
+ {
+ patches.Add(new Mod<DirectoryInfo>(modDir.Name, modDir));
+ Logger.Info?.Print(LogClass.ModLoader, $"Found {type} patch '{modDir.Name}'");
+ }
+ }
+
+ public static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir)
+ {
+ if (!titleDir.Exists) return;
+
+ var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer));
+ if (fsFile.Exists)
+ {
+ mods.RomfsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} RomFs>", fsFile));
+ }
+
+ fsFile = new FileInfo(Path.Combine(titleDir.FullName, ExefsContainer));
+ if (fsFile.Exists)
+ {
+ mods.ExefsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} ExeFs>", fsFile));
+ }
+
+ System.Text.StringBuilder types = new System.Text.StringBuilder(5);
+
+ foreach (var modDir in titleDir.EnumerateDirectories())
+ {
+ types.Clear();
+ Mod<DirectoryInfo> mod = new Mod<DirectoryInfo>("", null);
+
+ if (StrEquals(RomfsDir, modDir.Name))
+ {
+ mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} RomFs>", modDir));
+ types.Append('R');
+ }
+ else if (StrEquals(ExefsDir, modDir.Name))
+ {
+ mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>($"<{titleDir.Name} ExeFs>", modDir));
+ types.Append('E');
+ }
+ else if (StrEquals(CheatDir, modDir.Name))
+ {
+ for (int i = 0; i < QueryCheatsDir(mods, modDir); i++)
+ {
+ types.Append('C');
+ }
+ }
+ else
+ {
+ var romfs = new DirectoryInfo(Path.Combine(modDir.FullName, RomfsDir));
+ var exefs = new DirectoryInfo(Path.Combine(modDir.FullName, ExefsDir));
+ var cheat = new DirectoryInfo(Path.Combine(modDir.FullName, CheatDir));
+
+ if (romfs.Exists)
+ {
+ mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, romfs));
+ types.Append('R');
+ }
+
+ if (exefs.Exists)
+ {
+ mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(modDir.Name, exefs));
+ types.Append('E');
+ }
+
+ if (cheat.Exists)
+ {
+ for (int i = 0; i < QueryCheatsDir(mods, cheat); i++)
+ {
+ types.Append('C');
+ }
+ }
+ }
+
+ if (types.Length > 0) Logger.Info?.Print(LogClass.ModLoader, $"Found mod '{mod.Name}' [{types}]");
+ }
+ }
+
+ public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId)
+ {
+ if (!contentsDir.Exists) return;
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}");
+
+ var titleDir = FindTitleDir(contentsDir, $"{titleId:x16}");
+
+ if (titleDir != null)
+ {
+ QueryTitleDir(mods, titleDir);
+ }
+ }
+
+ private static int QueryCheatsDir(ModCache mods, DirectoryInfo cheatsDir)
+ {
+ if (!cheatsDir.Exists)
+ {
+ return 0;
+ }
+
+ int numMods = 0;
+
+ foreach (FileInfo file in cheatsDir.EnumerateFiles())
+ {
+ if (!StrEquals(CheatExtension, file.Extension))
+ {
+ continue;
+ }
+
+ string cheatId = Path.GetFileNameWithoutExtension(file.Name);
+
+ if (cheatId.Length != Cheat.CheatIdSize)
+ {
+ continue;
+ }
+
+ if (!ulong.TryParse(cheatId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
+ {
+ continue;
+ }
+
+ // A cheat file can contain several cheats for the same executable, so the file must be parsed in
+ // order to properly enumerate them.
+ mods.Cheats.AddRange(GetCheatsInFile(file));
+ }
+
+ return numMods;
+ }
+
+ private static IEnumerable<Cheat> GetCheatsInFile(FileInfo cheatFile)
+ {
+ string cheatName = DefaultCheatName;
+ List<string> instructions = new List<string>();
+ List<Cheat> cheats = new List<Cheat>();
+
+ using (StreamReader cheatData = cheatFile.OpenText())
+ {
+ string line;
+ while ((line = cheatData.ReadLine()) != null)
+ {
+ line = line.Trim();
+
+ if (line.StartsWith('['))
+ {
+ // This line starts a new cheat section.
+ if (!line.EndsWith(']') || line.Length < 3)
+ {
+ // Skip the entire file if there's any error while parsing the cheat file.
+
+ Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed");
+
+ return new List<Cheat>();
+ }
+
+ // Add the previous section to the list.
+ if (instructions.Count != 0)
+ {
+ cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
+ }
+
+ // Start a new cheat section.
+ cheatName = line.Substring(1, line.Length - 2);
+ instructions = new List<string>();
+ }
+ else if (line.Length > 0)
+ {
+ // The line contains an instruction.
+ instructions.Add(line);
+ }
+ }
+
+ // Add the last section being processed.
+ if (instructions.Count != 0)
+ {
+ cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions));
+ }
+ }
+
+ return cheats;
+ }
+
+ // Assumes searchDirPaths don't overlap
+ public static void CollectMods(Dictionary<ulong, ModCache> modCaches, PatchCache patches, params string[] searchDirPaths)
+ {
+ static bool IsPatchesDir(string name) => StrEquals(AmsNsoPatchDir, name) ||
+ StrEquals(AmsNroPatchDir, name) ||
+ StrEquals(AmsKipPatchDir, name);
+
+ static bool IsContentsDir(string name) => StrEquals(AmsContentsDir, name);
+
+ static bool TryQuery(DirectoryInfo searchDir, PatchCache patches, Dictionary<ulong, ModCache> modCaches)
+ {
+ if (IsContentsDir(searchDir.Name))
+ {
+ foreach (var (titleId, cache) in modCaches)
+ {
+ QueryContentsDir(cache, searchDir, titleId);
+ }
+
+ return true;
+ }
+ else if (IsPatchesDir(searchDir.Name))
+ {
+ QueryPatchDirs(patches, searchDir);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ foreach (var path in searchDirPaths)
+ {
+ var searchDir = new DirectoryInfo(path);
+ if (!searchDir.Exists)
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, $"Mod Search Dir '{searchDir.FullName}' doesn't exist");
+ continue;
+ }
+
+ if (!TryQuery(searchDir, patches, modCaches))
+ {
+ foreach (var subdir in searchDir.EnumerateDirectories())
+ {
+ TryQuery(subdir, patches, modCaches);
+ }
+ }
+ }
+
+ patches.Initialized = true;
+ }
+
+ public void CollectMods(IEnumerable<ulong> titles, params string[] searchDirPaths)
+ {
+ Clear();
+
+ foreach (ulong titleId in titles)
+ {
+ AppMods[titleId] = new ModCache();
+ }
+
+ CollectMods(AppMods, Patches, searchDirPaths);
+ }
+
+ internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage)
+ {
+ if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0)
+ {
+ return baseStorage;
+ }
+
+ var fileSet = new HashSet<string>();
+ var builder = new RomFsBuilder();
+ int count = 0;
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Title {titleId:X16}");
+
+ // Prioritize loose files first
+ foreach (var mod in mods.RomfsDirs)
+ {
+ using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName))
+ {
+ AddFiles(fs, mod.Name, fileSet, builder);
+ }
+ count++;
+ }
+
+ // Then files inside images
+ foreach (var mod in mods.RomfsContainers)
+ {
+ Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Title {titleId:X16}");
+ using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage()))
+ {
+ AddFiles(fs, mod.Name, fileSet, builder);
+ }
+ count++;
+ }
+
+ if (fileSet.Count == 0)
+ {
+ Logger.Info?.Print(LogClass.ModLoader, "No files found. Using base RomFS");
+
+ return baseStorage;
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Replaced {fileSet.Count} file(s) over {count} mod(s). Processing base storage...");
+
+ // And finally, the base romfs
+ var baseRom = new RomFsFileSystem(baseStorage);
+ foreach (var entry in baseRom.EnumerateEntries()
+ .Where(f => f.Type == DirectoryEntryType.File && !fileSet.Contains(f.FullPath))
+ .OrderBy(f => f.FullPath, StringComparer.Ordinal))
+ {
+ using var file = new UniqueRef<IFile>();
+
+ baseRom.OpenFile(ref file.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ builder.AddFile(entry.FullPath, file.Release());
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, "Building new RomFS...");
+ IStorage newStorage = builder.Build();
+ Logger.Info?.Print(LogClass.ModLoader, "Using modded RomFS");
+
+ return newStorage;
+ }
+
+ private static void AddFiles(IFileSystem fs, string modName, HashSet<string> fileSet, RomFsBuilder builder)
+ {
+ foreach (var entry in fs.EnumerateEntries()
+ .Where(f => f.Type == DirectoryEntryType.File)
+ .OrderBy(f => f.FullPath, StringComparer.Ordinal))
+ {
+ using var file = new UniqueRef<IFile>();
+
+ fs.OpenFile(ref file.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ if (fileSet.Add(entry.FullPath))
+ {
+ builder.AddFile(entry.FullPath, file.Release());
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, $" Skipped duplicate file '{entry.FullPath}' from '{modName}'", "ApplyRomFsMods");
+ }
+ }
+ }
+
+ internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs)
+ {
+ if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0)
+ {
+ return false;
+ }
+
+ if (mods.ExefsContainers.Count > 1)
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, "Multiple ExeFS partition replacements detected");
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Using replacement ExeFS partition");
+
+ exefs = new PartitionFileSystem(mods.ExefsContainers[0].Path.OpenRead().AsStorage());
+
+ return true;
+ }
+
+ public struct ModLoadResult
+ {
+ public BitVector32 Stubs;
+ public BitVector32 Replaces;
+ public MetaLoader Npdm;
+
+ public bool Modified => (Stubs.Data | Replaces.Data) != 0;
+ }
+
+ internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
+ {
+ ModLoadResult modLoadResult = new ModLoadResult
+ {
+ Stubs = new BitVector32(),
+ Replaces = new BitVector32()
+ };
+
+ if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
+ {
+ return modLoadResult;
+ }
+
+ if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
+ {
+ throw new ArgumentOutOfRangeException("NSO Count is incorrect");
+ }
+
+ var exeMods = mods.ExefsDirs;
+
+ foreach (var mod in exeMods)
+ {
+ for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
+ {
+ var nsoName = ProcessConst.ExeFsPrefixes[i];
+
+ FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
+ if (nsoFile.Exists)
+ {
+ if (modLoadResult.Replaces[1 << i])
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, $"Multiple replacements to '{nsoName}'");
+
+ continue;
+ }
+
+ modLoadResult.Replaces[1 << i] = true;
+
+ nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName);
+ Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced");
+ }
+
+ modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
+ }
+
+ FileInfo npdmFile = new FileInfo(Path.Combine(mod.Path.FullName, "main.npdm"));
+ if (npdmFile.Exists)
+ {
+ if (modLoadResult.Npdm != null)
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, "Multiple replacements to 'main.npdm'");
+
+ continue;
+ }
+
+ modLoadResult.Npdm = new MetaLoader();
+ modLoadResult.Npdm.Load(File.ReadAllBytes(npdmFile.FullName));
+
+ Logger.Info?.Print(LogClass.ModLoader, "main.npdm replaced");
+ }
+ }
+
+ for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
+ {
+ if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
+ {
+ Logger.Info?.Print(LogClass.ModLoader, $" NSO '{nsos[i].Name}' stubbed");
+ nsos[i] = null;
+ }
+ }
+
+ return modLoadResult;
+ }
+
+ internal void ApplyNroPatches(NroExecutable nro)
+ {
+ var nroPatches = Patches.NroPatches;
+
+ if (nroPatches.Count == 0) return;
+
+ // NRO patches aren't offset relative to header unlike NSO
+ // according to Atmosphere's ro patcher module
+ ApplyProgramPatches(nroPatches, 0, nro);
+ }
+
+ internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs)
+ {
+ IEnumerable<Mod<DirectoryInfo>> nsoMods = Patches.NsoPatches;
+
+ if (AppMods.TryGetValue(titleId, out ModCache mods))
+ {
+ nsoMods = nsoMods.Concat(mods.ExefsDirs);
+ }
+
+ // NSO patches are created with offset 0 according to Atmosphere's patcher module
+ // But `Program` doesn't contain the header which is 0x100 bytes. So, we adjust for that here
+ return ApplyProgramPatches(nsoMods, 0x100, programs);
+ }
+
+ internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine)
+ {
+ if (tamperInfo == null || tamperInfo.BuildIds == null || tamperInfo.CodeAddresses == null)
+ {
+ Logger.Error?.Print(LogClass.ModLoader, "Unable to install cheat because the associated process is invalid");
+
+ return;
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}");
+
+ if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0)
+ {
+ return;
+ }
+
+ var cheats = mods.Cheats;
+ var processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
+ .ToDictionary(x => x.k.Substring(0, Math.Min(Cheat.CheatIdSize, x.k.Length)), x => x.v);
+
+ foreach (var cheat in cheats)
+ {
+ string cheatId = Path.GetFileNameWithoutExtension(cheat.Path.Name).ToUpper();
+
+ if (!processExes.TryGetValue(cheatId, out ulong exeAddress))
+ {
+ Logger.Warning?.Print(LogClass.ModLoader, $"Skipping cheat '{cheat.Name}' because no executable matches its BuildId {cheatId} (check if the game title and version are correct)");
+
+ continue;
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'");
+
+ tamperMachine.InstallAtmosphereCheat(cheat.Name, cheatId, cheat.Instructions, tamperInfo, exeAddress);
+ }
+
+ EnableCheats(titleId, tamperMachine);
+ }
+
+ internal void EnableCheats(ulong titleId, TamperMachine tamperMachine)
+ {
+ var contentDirectory = FindTitleDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{titleId:x16}");
+ string enabledCheatsPath = Path.Combine(contentDirectory.FullName, CheatDir, "enabled.txt");
+
+ if (File.Exists(enabledCheatsPath))
+ {
+ tamperMachine.EnableCheats(File.ReadAllLines(enabledCheatsPath));
+ }
+ }
+
+ private static bool ApplyProgramPatches(IEnumerable<Mod<DirectoryInfo>> mods, int protectedOffset, params IExecutable[] programs)
+ {
+ int count = 0;
+
+ MemPatch[] patches = new MemPatch[programs.Length];
+
+ for (int i = 0; i < patches.Length; ++i)
+ {
+ patches[i] = new MemPatch();
+ }
+
+ var buildIds = programs.Select(p => p switch
+ {
+ NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()).TrimEnd('0'),
+ NroExecutable nro => Convert.ToHexString(nro.Header.BuildId).TrimEnd('0'),
+ _ => string.Empty
+ }).ToList();
+
+ int GetIndex(string buildId) => buildIds.FindIndex(id => id == buildId); // O(n) but list is small
+
+ // Collect patches
+ foreach (var mod in mods)
+ {
+ var patchDir = mod.Path;
+ foreach (var patchFile in patchDir.EnumerateFiles())
+ {
+ if (StrEquals(".ips", patchFile.Extension)) // IPS|IPS32
+ {
+ string filename = Path.GetFileNameWithoutExtension(patchFile.FullName).Split('.')[0];
+ string buildId = filename.TrimEnd('0');
+
+ int index = GetIndex(buildId);
+ if (index == -1)
+ {
+ continue;
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Matching IPS patch '{patchFile.Name}' in '{mod.Name}' bid={buildId}");
+
+ using var fs = patchFile.OpenRead();
+ using var reader = new BinaryReader(fs);
+
+ var patcher = new IpsPatcher(reader);
+ patcher.AddPatches(patches[index]);
+ }
+ else if (StrEquals(".pchtxt", patchFile.Extension)) // IPSwitch
+ {
+ using var fs = patchFile.OpenRead();
+ using var reader = new StreamReader(fs);
+
+ var patcher = new IPSwitchPatcher(reader);
+
+ int index = GetIndex(patcher.BuildId);
+ if (index == -1)
+ {
+ continue;
+ }
+
+ Logger.Info?.Print(LogClass.ModLoader, $"Matching IPSwitch patch '{patchFile.Name}' in '{mod.Name}' bid={patcher.BuildId}");
+
+ patcher.AddPatches(patches[index]);
+ }
+ }
+ }
+
+ // Apply patches
+ for (int i = 0; i < programs.Length; ++i)
+ {
+ count += patches[i].Patch(programs[i].Program, protectedOffset);
+ }
+
+ return count > 0;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/ResultCode.cs b/src/Ryujinx.HLE/HOS/ResultCode.cs
new file mode 100644
index 00000000..4004357b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/ResultCode.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS
+{
+ public enum ResultCode
+ {
+ OsModuleId = 3,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ NotAllocated = (1023 << ErrorCodeShift) | OsModuleId
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/ServiceCtx.cs b/src/Ryujinx.HLE/HOS/ServiceCtx.cs
new file mode 100644
index 00000000..659b212a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/ServiceCtx.cs
@@ -0,0 +1,40 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Memory;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS
+{
+ class ServiceCtx
+ {
+ public Switch Device { get; }
+ public KProcess Process { get; }
+ public IVirtualMemoryManager Memory { get; }
+ public KThread Thread { get; }
+ public IpcMessage Request { get; }
+ public IpcMessage Response { get; }
+ public BinaryReader RequestData { get; }
+ public BinaryWriter ResponseData { get; }
+
+ public ServiceCtx(
+ Switch device,
+ KProcess process,
+ IVirtualMemoryManager memory,
+ KThread thread,
+ IpcMessage request,
+ IpcMessage response,
+ BinaryReader requestData,
+ BinaryWriter responseData)
+ {
+ Device = device;
+ Process = process;
+ Memory = memory;
+ Thread = thread;
+ Request = request;
+ Response = response;
+ RequestData = requestData;
+ ResponseData = responseData;
+ }
+ }
+} \ No newline at end of file
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
diff --git a/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs
new file mode 100644
index 00000000..5704ef4b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs
@@ -0,0 +1,42 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ class AppletStateMgr
+ {
+ public ConcurrentQueue<AppletMessage> Messages { get; }
+
+ public FocusState FocusState { get; private set; }
+
+ public KEvent MessageEvent { get; }
+
+ public IdDictionary AppletResourceUserIds { get; }
+
+ public IdDictionary IndirectLayerHandles { get; }
+
+ public AppletStateMgr(Horizon system)
+ {
+ Messages = new ConcurrentQueue<AppletMessage>();
+ MessageEvent = new KEvent(system.KernelContext);
+
+ AppletResourceUserIds = new IdDictionary();
+ IndirectLayerHandles = new IdDictionary();
+ }
+
+ public void SetFocus(bool isFocused)
+ {
+ FocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus;
+
+ Messages.Enqueue(AppletMessage.FocusStateChanged);
+
+ if (isFocused)
+ {
+ Messages.Enqueue(AppletMessage.ChangeIntoForeground);
+ }
+
+ MessageEvent.ReadableEvent.Signal();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs b/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs
new file mode 100644
index 00000000..4d7a7e2f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public enum ColorSet
+ {
+ BasicWhite = 0,
+ BasicBlack = 1
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs b/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs
new file mode 100644
index 00000000..ba35ea6b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ // nn::settings::KeyboardLayout
+ public enum KeyboardLayout
+ {
+ Default = 0,
+ EnglishUs,
+ EnglishUsInternational,
+ EnglishUk,
+ French,
+ FrenchCa,
+ Spanish,
+ SpanishLatin,
+ German,
+ Italian,
+ Portuguese,
+ Russian,
+ Korean,
+ ChineseSimplified,
+ ChineseTraditional,
+
+ Min = Default,
+ Max = ChineseTraditional
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs b/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs
new file mode 100644
index 00000000..dd6ed8fa
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ // nn::settings::RegionCode
+ public enum RegionCode
+ {
+ Japan,
+ USA,
+ Europe,
+ Australia,
+ China,
+ Korea,
+ Taiwan,
+
+ Min = Japan,
+ Max = Taiwan
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs b/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs
new file mode 100644
index 00000000..3f755105
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public enum SystemLanguage
+ {
+ Japanese,
+ AmericanEnglish,
+ French,
+ German,
+ Italian,
+ Spanish,
+ Chinese,
+ Korean,
+ Dutch,
+ Portuguese,
+ Russian,
+ Taiwanese,
+ BritishEnglish,
+ CanadianFrench,
+ LatinAmericanSpanish,
+ SimplifiedChinese,
+ TraditionalChinese,
+ BrazilianPortuguese
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
new file mode 100644
index 00000000..6627700f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
@@ -0,0 +1,90 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public class SystemStateMgr
+ {
+ internal static string[] LanguageCodes = new string[]
+ {
+ "ja",
+ "en-US",
+ "fr",
+ "de",
+ "it",
+ "es",
+ "zh-CN",
+ "ko",
+ "nl",
+ "pt",
+ "ru",
+ "zh-TW",
+ "en-GB",
+ "fr-CA",
+ "es-419",
+ "zh-Hans",
+ "zh-Hant",
+ "pt-BR"
+ };
+
+ internal long DesiredKeyboardLayout { get; private set; }
+
+ internal SystemLanguage DesiredSystemLanguage { get; private set; }
+
+ internal long DesiredLanguageCode { get; private set; }
+
+ internal uint DesiredRegionCode { get; private set; }
+
+ public TitleLanguage DesiredTitleLanguage { get; private set; }
+
+ public bool DockedMode { get; set; }
+
+ public ColorSet ThemeColor { get; set; }
+
+ public string DeviceNickName { get; set; }
+
+ public SystemStateMgr()
+ {
+ // TODO: Let user specify fields.
+ DesiredKeyboardLayout = (long)KeyboardLayout.Default;
+ DeviceNickName = "Ryujinx's Switch";
+ }
+
+ public void SetLanguage(SystemLanguage language)
+ {
+ DesiredSystemLanguage = language;
+ DesiredLanguageCode = GetLanguageCode((int)DesiredSystemLanguage);
+
+ DesiredTitleLanguage = language switch
+ {
+ SystemLanguage.Taiwanese or
+ SystemLanguage.TraditionalChinese => TitleLanguage.TraditionalChinese,
+ SystemLanguage.Chinese or
+ SystemLanguage.SimplifiedChinese => TitleLanguage.SimplifiedChinese,
+ _ => Enum.Parse<TitleLanguage>(Enum.GetName<SystemLanguage>(language)),
+ };
+ }
+
+ public void SetRegion(RegionCode region)
+ {
+ DesiredRegionCode = (uint)region;
+ }
+
+ internal static long GetLanguageCode(int index)
+ {
+ if ((uint)index >= LanguageCodes.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ long code = 0;
+ int shift = 0;
+
+ foreach (char chr in LanguageCodes[index])
+ {
+ code |= (long)(byte)chr << shift++ * 8;
+ }
+
+ return code;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs b/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs
new file mode 100644
index 00000000..c612259b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public enum TitleLanguage
+ {
+ AmericanEnglish,
+ BritishEnglish,
+ Japanese,
+ French,
+ German,
+ LatinAmericanSpanish,
+ Spanish,
+ Italian,
+ Dutch,
+ CanadianFrench,
+ Portuguese,
+ Russian,
+ Korean,
+ TraditionalChinese,
+ SimplifiedChinese,
+ BrazilianPortuguese
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs
new file mode 100644
index 00000000..7d7af208
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs
@@ -0,0 +1,152 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.CodeEmitters;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class AtmosphereCompiler
+ {
+ private ulong _exeAddress;
+ private ulong _heapAddress;
+ private ulong _aliasAddress;
+ private ulong _aslrAddress;
+ private ITamperedProcess _process;
+
+ public AtmosphereCompiler(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process)
+ {
+ _exeAddress = exeAddress;
+ _heapAddress = heapAddress;
+ _aliasAddress = aliasAddress;
+ _aslrAddress = aslrAddress;
+ _process = process;
+ }
+
+ public ITamperProgram Compile(string name, IEnumerable<string> rawInstructions)
+ {
+ string[] addresses = new string[]
+ {
+ $" Executable address: 0x{_exeAddress:X16}",
+ $" Heap address : 0x{_heapAddress:X16}",
+ $" Alias address : 0x{_aliasAddress:X16}",
+ $" Aslr address : 0x{_aslrAddress:X16}"
+ };
+
+ Logger.Debug?.Print(LogClass.TamperMachine, $"Compiling Atmosphere cheat {name}...\n{string.Join('\n', addresses)}");
+
+ try
+ {
+ return CompileImpl(name, rawInstructions);
+ }
+ catch(TamperCompilationException exception)
+ {
+ // Just print the message without the stack trace.
+ Logger.Error?.Print(LogClass.TamperMachine, exception.Message);
+ }
+ catch (Exception exception)
+ {
+ Logger.Error?.Print(LogClass.TamperMachine, exception.ToString());
+ }
+
+ Logger.Error?.Print(LogClass.TamperMachine, "There was a problem while compiling the Atmosphere cheat");
+
+ return null;
+ }
+
+ private ITamperProgram CompileImpl(string name, IEnumerable<string> rawInstructions)
+ {
+ CompilationContext context = new CompilationContext(_exeAddress, _heapAddress, _aliasAddress, _aslrAddress, _process);
+ context.BlockStack.Push(new OperationBlock(null));
+
+ // Parse the instructions.
+
+ foreach (string rawInstruction in rawInstructions)
+ {
+ Logger.Debug?.Print(LogClass.TamperMachine, $"Compiling instruction {rawInstruction}");
+
+ byte[] instruction = InstructionHelper.ParseRawInstruction(rawInstruction);
+ CodeType codeType = InstructionHelper.GetCodeType(instruction);
+
+ switch (codeType)
+ {
+ case CodeType.StoreConstantToAddress:
+ StoreConstantToAddress.Emit(instruction, context);
+ break;
+ case CodeType.BeginMemoryConditionalBlock:
+ BeginConditionalBlock.Emit(instruction, context);
+ break;
+ case CodeType.EndConditionalBlock:
+ EndConditionalBlock.Emit(instruction, context);
+ break;
+ case CodeType.StartEndLoop:
+ StartEndLoop.Emit(instruction, context);
+ break;
+ case CodeType.LoadRegisterWithContant:
+ LoadRegisterWithConstant.Emit(instruction, context);
+ break;
+ case CodeType.LoadRegisterWithMemory:
+ LoadRegisterWithMemory.Emit(instruction, context);
+ break;
+ case CodeType.StoreConstantToMemory:
+ StoreConstantToMemory.Emit(instruction, context);
+ break;
+ case CodeType.LegacyArithmetic:
+ LegacyArithmetic.Emit(instruction, context);
+ break;
+ case CodeType.BeginKeypressConditionalBlock:
+ BeginConditionalBlock.Emit(instruction, context);
+ break;
+ case CodeType.Arithmetic:
+ Arithmetic.Emit(instruction, context);
+ break;
+ case CodeType.StoreRegisterToMemory:
+ StoreRegisterToMemory.Emit(instruction, context);
+ break;
+ case CodeType.BeginRegisterConditionalBlock:
+ BeginConditionalBlock.Emit(instruction, context);
+ break;
+ case CodeType.SaveOrRestoreRegister:
+ SaveOrRestoreRegister.Emit(instruction, context);
+ break;
+ case CodeType.SaveOrRestoreRegisterWithMask:
+ SaveOrRestoreRegisterWithMask.Emit(instruction, context);
+ break;
+ case CodeType.ReadOrWriteStaticRegister:
+ ReadOrWriteStaticRegister.Emit(instruction, context);
+ break;
+ case CodeType.PauseProcess:
+ PauseProcess.Emit(instruction, context);
+ break;
+ case CodeType.ResumeProcess:
+ ResumeProcess.Emit(instruction, context);
+ break;
+ case CodeType.DebugLog:
+ DebugLog.Emit(instruction, context);
+ break;
+ default:
+ throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat");
+ }
+ }
+
+ // Initialize only the registers used.
+
+ Value<ulong> zero = new Value<ulong>(0UL);
+ int position = 0;
+
+ foreach (Register register in context.Registers.Values)
+ {
+ context.CurrentOperations.Insert(position, new OpMov<ulong>(register, zero));
+ position++;
+ }
+
+ if (context.BlockStack.Count != 1)
+ {
+ throw new TamperCompilationException($"Reached end of compilation with unmatched conditional(s) or loop(s)");
+ }
+
+ return new AtmosphereProgram(name, _process, context.PressedKeys, new Block(context.CurrentOperations));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs
new file mode 100644
index 00000000..a2aa73a4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs
@@ -0,0 +1,33 @@
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class AtmosphereProgram : ITamperProgram
+ {
+ private Parameter<long> _pressedKeys;
+ private IOperation _entryPoint;
+
+ public string Name { get; }
+ public bool TampersCodeMemory { get; set; } = false;
+ public ITamperedProcess Process { get; }
+ public bool IsEnabled { get; set; }
+
+ public AtmosphereProgram(string name, ITamperedProcess process, Parameter<long> pressedKeys, IOperation entryPoint)
+ {
+ Name = name;
+ Process = process;
+ _pressedKeys = pressedKeys;
+ _entryPoint = entryPoint;
+ }
+
+ public void Execute(ControllerKeys pressedKeys)
+ {
+ if (IsEnabled)
+ {
+ _pressedKeys.Value = (long)pressedKeys;
+ _entryPoint.Execute();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs
new file mode 100644
index 00000000..b7d46d3a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs
@@ -0,0 +1,105 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 9 allows performing arithmetic on registers.
+ /// </summary>
+ class Arithmetic
+ {
+ private const int OperationWidthIndex = 1;
+ private const int OperationTypeIndex = 2;
+ private const int DestinationRegisterIndex = 3;
+ private const int LeftHandSideRegisterIndex = 4;
+ private const int UseImmediateAsRhsIndex = 5;
+ private const int RightHandSideRegisterIndex = 6;
+ private const int RightHandSideImmediateIndex = 8;
+
+ private const int RightHandSideImmediate8 = 8;
+ private const int RightHandSideImmediate16 = 16;
+
+ private const byte Add = 0; // lhs + rhs
+ private const byte Sub = 1; // lhs - rhs
+ private const byte Mul = 2; // lhs * rhs
+ private const byte Lsh = 3; // lhs << rhs
+ private const byte Rsh = 4; // lhs >> rhs
+ private const byte And = 5; // lhs & rhs
+ private const byte Or = 6; // lhs | rhs
+ private const byte Not = 7; // ~lhs (discards right-hand operand)
+ private const byte Xor = 8; // lhs ^ rhs
+ private const byte Mov = 9; // lhs (discards right-hand operand)
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 9TCRS0s0
+ // T: Width of arithmetic operation(1, 2, 4, or 8 bytes).
+ // C: Arithmetic operation to apply, see below.
+ // R: Register to store result in.
+ // S: Register to use as left - hand operand.
+ // s: Register to use as right - hand operand.
+
+ // 9TCRS100 VVVVVVVV (VVVVVVVV)
+ // T: Width of arithmetic operation(1, 2, 4, or 8 bytes).
+ // C: Arithmetic operation to apply, see below.
+ // R: Register to store result in.
+ // S: Register to use as left - hand operand.
+ // V: Value to use as right - hand operand.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ byte operation = instruction[OperationTypeIndex];
+ Register destinationRegister = context.GetRegister(instruction[DestinationRegisterIndex]);
+ Register leftHandSideRegister = context.GetRegister(instruction[LeftHandSideRegisterIndex]);
+ byte rightHandSideIsImmediate = instruction[UseImmediateAsRhsIndex];
+ IOperand rightHandSideOperand;
+
+ switch (rightHandSideIsImmediate)
+ {
+ case 0:
+ // Use a register as right-hand side.
+ rightHandSideOperand = context.GetRegister(instruction[RightHandSideRegisterIndex]);
+ break;
+ case 1:
+ // Use an immediate as right-hand side.
+ int immediateSize = operationWidth <= 4 ? RightHandSideImmediate8 : RightHandSideImmediate16;
+ ulong immediate = InstructionHelper.GetImmediate(instruction, RightHandSideImmediateIndex, immediateSize);
+ rightHandSideOperand = new Value<ulong>(immediate);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid right-hand side switch {rightHandSideIsImmediate} in Atmosphere cheat");
+ }
+
+ void Emit(Type operationType, IOperand rhs = null)
+ {
+ List<IOperand> operandList = new List<IOperand>();
+ operandList.Add(destinationRegister);
+ operandList.Add(leftHandSideRegister);
+
+ if (rhs != null)
+ {
+ operandList.Add(rhs);
+ }
+
+ InstructionHelper.Emit(operationType, operationWidth, context, operandList.ToArray());
+ }
+
+ switch (operation)
+ {
+ case Add: Emit(typeof(OpAdd<>), rightHandSideOperand); break;
+ case Sub: Emit(typeof(OpSub<>), rightHandSideOperand); break;
+ case Mul: Emit(typeof(OpMul<>), rightHandSideOperand); break;
+ case Lsh: Emit(typeof(OpLsh<>), rightHandSideOperand); break;
+ case Rsh: Emit(typeof(OpRsh<>), rightHandSideOperand); break;
+ case And: Emit(typeof(OpAnd<>), rightHandSideOperand); break;
+ case Or: Emit(typeof(OpOr<> ), rightHandSideOperand); break;
+ case Not: Emit(typeof(OpNot<>) ); break;
+ case Xor: Emit(typeof(OpXor<>), rightHandSideOperand); break;
+ case Mov: Emit(typeof(OpMov<>) ); break;
+ default:
+ throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs
new file mode 100644
index 00000000..5439821c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Marks the begin of a conditional block (started by Code Type 1, Code Type 8 or Code Type C0).
+ /// </summary>
+ class BeginConditionalBlock
+ {
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // Just start a new compilation block and parse the instruction itself at the end.
+ context.BlockStack.Push(new OperationBlock(instruction));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs
new file mode 100644
index 00000000..533b362a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs
@@ -0,0 +1,87 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xFFF writes a debug log.
+ /// </summary>
+ class DebugLog
+ {
+ private const int OperationWidthIndex = 3;
+ private const int LogIdIndex = 4;
+ private const int OperandTypeIndex = 5;
+ private const int RegisterOrMemoryRegionIndex = 6;
+ private const int OffsetRegisterOrImmediateIndex = 7;
+
+ private const int MemoryRegionWithOffsetImmediate = 0;
+ private const int MemoryRegionWithOffsetRegister = 1;
+ private const int AddressRegisterWithOffsetImmediate = 2;
+ private const int AddressRegisterWithOffsetRegister = 3;
+ private const int ValueRegister = 4;
+
+ private const int OffsetImmediateSize = 9;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // FFFTIX##
+ // FFFTI0Ma aaaaaaaa
+ // FFFTI1Mr
+ // FFFTI2Ra aaaaaaaa
+ // FFFTI3Rr
+ // FFFTI4V0
+ // T: Width of memory write (1, 2, 4, or 8 bytes).
+ // I: Log id.
+ // X: Operand Type, see below.
+ // M: Memory Type (operand types 0 and 1).
+ // R: Address Register (operand types 2 and 3).
+ // a: Relative Address (operand types 0 and 2).
+ // r: Offset Register (operand types 1 and 3).
+ // V: Value Register (operand type 4).
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ byte logId = instruction[LogIdIndex];
+ byte operandType = instruction[OperandTypeIndex];
+ byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex];
+ byte offsetRegisterIndex = instruction[OffsetRegisterOrImmediateIndex];
+ ulong immediate;
+ Register addressRegister;
+ Register offsetRegister;
+ IOperand sourceOperand;
+
+ switch (operandType)
+ {
+ case MemoryRegionWithOffsetImmediate:
+ // *(?x + #a)
+ immediate = InstructionHelper.GetImmediate(instruction, OffsetRegisterOrImmediateIndex, OffsetImmediateSize);
+ sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, immediate, context);
+ break;
+ case MemoryRegionWithOffsetRegister:
+ // *(?x + $r)
+ offsetRegister = context.GetRegister(offsetRegisterIndex);
+ sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetRegister, context);
+ break;
+ case AddressRegisterWithOffsetImmediate:
+ // *($R + #a)
+ addressRegister = context.GetRegister(registerOrMemoryRegion);
+ immediate = InstructionHelper.GetImmediate(instruction, OffsetRegisterOrImmediateIndex, OffsetImmediateSize);
+ sourceOperand = MemoryHelper.EmitPointer(addressRegister, immediate, context);
+ break;
+ case AddressRegisterWithOffsetRegister:
+ // *($R + $r)
+ addressRegister = context.GetRegister(registerOrMemoryRegion);
+ offsetRegister = context.GetRegister(offsetRegisterIndex);
+ sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context);
+ break;
+ case ValueRegister:
+ // $V
+ sourceOperand = context.GetRegister(registerOrMemoryRegion);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid operand type {operandType} in Atmosphere cheat");
+ }
+
+ InstructionHelper.Emit(typeof(OpLog<>), operationWidth, context, logId, sourceOperand);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs
new file mode 100644
index 00000000..a25dddde
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs
@@ -0,0 +1,91 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 2 marks the end of a conditional block (started by Code Type 1, Code Type 8 or Code Type C0).
+ /// </summary>
+ class EndConditionalBlock
+ {
+ const int TerminationTypeIndex = 1;
+
+ private const byte End = 0; // True end of the conditional.
+ private const byte Else = 1; // End of the 'then' block and beginning of 'else' block.
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ Emit(instruction, context, null);
+ }
+
+ private static void Emit(byte[] instruction, CompilationContext context, IEnumerable<IOperation> operationsElse)
+ {
+ // 2X000000
+ // X: End type (0 = End, 1 = Else).
+
+ byte terminationType = instruction[TerminationTypeIndex];
+
+ switch (terminationType)
+ {
+ case End:
+ break;
+ case Else:
+ // Start a new operation block with the 'else' instruction to signal that there is the 'then' block just above it.
+ context.BlockStack.Push(new OperationBlock(instruction));
+ return;
+ default:
+ throw new TamperCompilationException($"Unknown conditional termination type {terminationType}");
+ }
+
+ // Use the conditional begin instruction stored in the stack.
+ var upperInstruction = context.CurrentBlock.BaseInstruction;
+ CodeType codeType = InstructionHelper.GetCodeType(upperInstruction);
+
+ // Pop the current block of operations from the stack so control instructions
+ // for the conditional can be emitted in the upper block.
+ IEnumerable<IOperation> operations = context.CurrentOperations;
+ context.BlockStack.Pop();
+
+ // If the else operations are already set, then the upper block must not be another end.
+ if (operationsElse != null && codeType == CodeType.EndConditionalBlock)
+ {
+ throw new TamperCompilationException($"Expected an upper 'if' conditional instead of 'end conditional'");
+ }
+
+ ICondition condition;
+
+ switch (codeType)
+ {
+ case CodeType.BeginMemoryConditionalBlock:
+ condition = MemoryConditional.Emit(upperInstruction, context);
+ break;
+ case CodeType.BeginKeypressConditionalBlock:
+ condition = KeyPressConditional.Emit(upperInstruction, context);
+ break;
+ case CodeType.BeginRegisterConditionalBlock:
+ condition = RegisterConditional.Emit(upperInstruction, context);
+ break;
+ case CodeType.EndConditionalBlock:
+ terminationType = upperInstruction[TerminationTypeIndex];
+ // If there is an end instruction above then it must be an else.
+ if (terminationType != Else)
+ {
+ throw new TamperCompilationException($"Expected an upper 'else' conditional instead of {terminationType}");
+ }
+ // Re-run the Emit with the else operations set.
+ Emit(instruction, context, operations);
+ return;
+ default:
+ throw new TamperCompilationException($"Conditional end does not match code type {codeType} in Atmosphere cheat");
+ }
+
+ // Create a conditional block with the current operations and nest it in the upper
+ // block of the stack.
+
+ IfBlock block = new IfBlock(condition, operations, operationsElse);
+ context.CurrentOperations.Add(block);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs
new file mode 100644
index 00000000..a1758665
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs
@@ -0,0 +1,26 @@
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 8 enters or skips a conditional block based on whether a key combination is pressed.
+ /// </summary>
+ class KeyPressConditional
+ {
+ private const int InputMaskIndex = 1;
+
+ private const int InputMaskSize = 7;
+
+ public static ICondition Emit(byte[] instruction, CompilationContext context)
+ {
+ // 8kkkkkkk
+ // k: Keypad mask to check against, see below.
+ // Note that for multiple button combinations, the bitmasks should be ORd together.
+ // The Keypad Values are the direct output of hidKeysDown().
+
+ ulong inputMask = InstructionHelper.GetImmediate(instruction, InputMaskIndex, InputMaskSize);
+
+ return new InputMask((long)inputMask, context.PressedKeys);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs
new file mode 100644
index 00000000..479c80ec
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs
@@ -0,0 +1,57 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 7 allows performing arithmetic on registers. However, it has been deprecated by Code
+ /// type 9, and is only kept for backwards compatibility.
+ /// </summary>
+ class LegacyArithmetic
+ {
+ const int OperationWidthIndex = 1;
+ const int DestinationRegisterIndex = 3;
+ const int OperationTypeIndex = 4;
+ const int ValueImmediateIndex = 8;
+
+ const int ValueImmediateSize = 8;
+
+ private const byte Add = 0; // reg += rhs
+ private const byte Sub = 1; // reg -= rhs
+ private const byte Mul = 2; // reg *= rhs
+ private const byte Lsh = 3; // reg <<= rhs
+ private const byte Rsh = 4; // reg >>= rhs
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 7T0RC000 VVVVVVVV
+ // T: Width of arithmetic operation(1, 2, 4, or 8 bytes).
+ // R: Register to apply arithmetic to.
+ // C: Arithmetic operation to apply, see below.
+ // V: Value to use for arithmetic operation.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ Register register = context.GetRegister(instruction[DestinationRegisterIndex]);
+ byte operation = instruction[OperationTypeIndex];
+ ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize);
+ Value<ulong> rightHandSideValue = new Value<ulong>(immediate);
+
+ void Emit(Type operationType)
+ {
+ InstructionHelper.Emit(operationType, operationWidth, context, register, register, rightHandSideValue);
+ }
+
+ switch (operation)
+ {
+ case Add: Emit(typeof(OpAdd<>)); break;
+ case Sub: Emit(typeof(OpSub<>)); break;
+ case Mul: Emit(typeof(OpMul<>)); break;
+ case Lsh: Emit(typeof(OpLsh<>)); break;
+ case Rsh: Emit(typeof(OpRsh<>)); break;
+ default:
+ throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs
new file mode 100644
index 00000000..e4a86d7b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs
@@ -0,0 +1,28 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 4 allows setting a register to a constant value.
+ /// </summary>
+ class LoadRegisterWithConstant
+ {
+ const int RegisterIndex = 3;
+ const int ValueImmediateIndex = 8;
+
+ const int ValueImmediateSize = 16;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 400R0000 VVVVVVVV VVVVVVVV
+ // R: Register to use.
+ // V: Value to load.
+
+ Register destinationRegister = context.GetRegister(instruction[RegisterIndex]);
+ ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize);
+ Value<ulong> sourceValue = new Value<ulong>(immediate);
+
+ context.CurrentOperations.Add(new OpMov<ulong>(destinationRegister, sourceValue));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs
new file mode 100644
index 00000000..87b37a1e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs
@@ -0,0 +1,58 @@
+using Ryujinx.HLE.Exceptions;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 5 allows loading a value from memory into a register, either using a fixed address or by
+ /// dereferencing the destination register.
+ /// </summary>
+ class LoadRegisterWithMemory
+ {
+ private const int OperationWidthIndex = 1;
+ private const int MemoryRegionIndex = 2;
+ private const int DestinationRegisterIndex = 3;
+ private const int UseDestinationAsSourceIndex = 4;
+ private const int OffsetImmediateIndex = 6;
+
+ private const int OffsetImmediateSize = 10;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 5TMR00AA AAAAAAAA
+ // T: Width of memory read (1, 2, 4, or 8 bytes).
+ // M: Memory region to write to (0 = Main NSO, 1 = Heap).
+ // R: Register to load value into.
+ // A: Immediate offset to use from memory region base.
+
+ // 5TMR10AA AAAAAAAA
+ // T: Width of memory read(1, 2, 4, or 8 bytes).
+ // M: Ignored.
+ // R: Register to use as base address and to load value into.
+ // A: Immediate offset to use from register R.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex];
+ Register destinationRegister = context.GetRegister(instruction[DestinationRegisterIndex]);
+ byte useDestinationAsSourceIndex = instruction[UseDestinationAsSourceIndex];
+ ulong address = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize);
+
+ Pointer sourceMemory;
+
+ switch (useDestinationAsSourceIndex)
+ {
+ case 0:
+ // Don't use the source register as an additional address offset.
+ sourceMemory = MemoryHelper.EmitPointer(memoryRegion, address, context);
+ break;
+ case 1:
+ // Use the source register as the base address.
+ sourceMemory = MemoryHelper.EmitPointer(destinationRegister, address, context);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid source mode {useDestinationAsSourceIndex} in Atmosphere cheat");
+ }
+
+ InstructionHelper.EmitMov(operationWidth, context, destinationRegister, sourceMemory);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs
new file mode 100644
index 00000000..2048a67b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs
@@ -0,0 +1,45 @@
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 1 performs a comparison of the contents of memory to a static value.
+ /// If the condition is not met, all instructions until the appropriate conditional block terminator
+ /// are skipped.
+ /// </summary>
+ class MemoryConditional
+ {
+ private const int OperationWidthIndex = 1;
+ private const int MemoryRegionIndex = 2;
+ private const int ComparisonTypeIndex = 3;
+ private const int OffsetImmediateIndex = 6;
+ private const int ValueImmediateIndex = 16;
+
+ private const int OffsetImmediateSize = 10;
+ private const int ValueImmediateSize4 = 8;
+ private const int ValueImmediateSize8 = 16;
+
+ public static ICondition Emit(byte[] instruction, CompilationContext context)
+ {
+ // 1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV)
+ // T: Width of memory write (1, 2, 4, or 8 bytes).
+ // M: Memory region to write to (0 = Main NSO, 1 = Heap).
+ // C: Condition to use, see below.
+ // A: Immediate offset to use from memory region base.
+ // V: Value to compare to.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex];
+ Comparison comparison = (Comparison)instruction[ComparisonTypeIndex];
+
+ ulong address = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize);
+ Pointer sourceMemory = MemoryHelper.EmitPointer(memoryRegion, address, context);
+
+ int valueSize = operationWidth <= 4 ? ValueImmediateSize4 : ValueImmediateSize8;
+ ulong value = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueSize);
+ Value<ulong> compareToValue = new Value<ulong>(value);
+
+ return InstructionHelper.CreateCondition(comparison, operationWidth, sourceMemory, compareToValue);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs
new file mode 100644
index 00000000..14f99394
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs
@@ -0,0 +1,17 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xFF0 pauses the current process.
+ /// </summary>
+ class PauseProcess
+ {
+ // FF0?????
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ context.CurrentOperations.Add(new OpProcCtrl(context.Process, true));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs
new file mode 100644
index 00000000..67775df7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs
@@ -0,0 +1,47 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xC3 reads or writes a static register with a given register.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Static Registers).
+ /// </summary>
+ class ReadOrWriteStaticRegister
+ {
+ private const int StaticRegisterIndex = 5;
+ private const int RegisterIndex = 7;
+
+ private const byte FirstWriteRegister = 0x80;
+
+ private const int StaticRegisterSize = 2;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // C3000XXx
+ // XX: Static register index, 0x00 to 0x7F for reading or 0x80 to 0xFF for writing.
+ // x: Register index.
+
+ ulong staticRegisterIndex = InstructionHelper.GetImmediate(instruction, StaticRegisterIndex, StaticRegisterSize);
+ Register register = context.GetRegister(instruction[RegisterIndex]);
+
+ IOperand sourceRegister;
+ IOperand destinationRegister;
+
+ if (staticRegisterIndex < FirstWriteRegister)
+ {
+ // Read from static register.
+ sourceRegister = context.GetStaticRegister((byte)staticRegisterIndex);
+ destinationRegister = register;
+ }
+ else
+ {
+ // Write to static register.
+ sourceRegister = register;
+ destinationRegister = context.GetStaticRegister((byte)(staticRegisterIndex - FirstWriteRegister));
+ }
+
+ context.CurrentOperations.Add(new OpMov<ulong>(destinationRegister, sourceRegister));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs
new file mode 100644
index 00000000..fcd3a9eb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs
@@ -0,0 +1,106 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xC0 performs a comparison of the contents of a register and another value.
+ /// This code support multiple operand types, see below. If the condition is not met,
+ /// all instructions until the appropriate conditional block terminator are skipped.
+ /// </summary>
+ class RegisterConditional
+ {
+ private const int OperationWidthIndex = 2;
+ private const int ComparisonTypeIndex = 3;
+ private const int SourceRegisterIndex = 4;
+ private const int OperandTypeIndex = 5;
+ private const int RegisterOrMemoryRegionIndex = 6;
+ private const int OffsetImmediateIndex = 7;
+ private const int ValueImmediateIndex = 8;
+
+ private const int MemoryRegionWithOffsetImmediate = 0;
+ private const int MemoryRegionWithOffsetRegister = 1;
+ private const int AddressRegisterWithOffsetImmediate = 2;
+ private const int AddressRegisterWithOffsetRegister = 3;
+ private const int OffsetImmediate = 4;
+ private const int AddressRegister = 5;
+
+ private const int OffsetImmediateSize = 9;
+ private const int ValueImmediateSize8 = 8;
+ private const int ValueImmediateSize16 = 16;
+
+ public static ICondition Emit(byte[] instruction, CompilationContext context)
+ {
+ // C0TcSX##
+ // C0TcS0Ma aaaaaaaa
+ // C0TcS1Mr
+ // C0TcS2Ra aaaaaaaa
+ // C0TcS3Rr
+ // C0TcS400 VVVVVVVV (VVVVVVVV)
+ // C0TcS5X0
+ // T: Width of memory write(1, 2, 4, or 8 bytes).
+ // c: Condition to use, see below.
+ // S: Source Register.
+ // X: Operand Type, see below.
+ // M: Memory Type(operand types 0 and 1).
+ // R: Address Register(operand types 2 and 3).
+ // a: Relative Address(operand types 0 and 2).
+ // r: Offset Register(operand types 1 and 3).
+ // X: Other Register(operand type 5).
+ // V: Value to compare to(operand type 4).
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ Comparison comparison = (Comparison)instruction[ComparisonTypeIndex];
+ Register sourceRegister = context.GetRegister(instruction[SourceRegisterIndex]);
+ byte operandType = instruction[OperandTypeIndex];
+ byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex];
+ byte offsetRegisterIndex = instruction[OffsetImmediateIndex];
+ ulong offsetImmediate;
+ ulong valueImmediate;
+ int valueImmediateSize;
+ Register addressRegister;
+ Register offsetRegister;
+ IOperand sourceOperand;
+
+ switch (operandType)
+ {
+ case MemoryRegionWithOffsetImmediate:
+ // *(?x + #a)
+ offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize);
+ sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetImmediate, context);
+ break;
+ case MemoryRegionWithOffsetRegister:
+ // *(?x + $r)
+ offsetRegister = context.GetRegister(offsetRegisterIndex);
+ sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetRegister, context);
+ break;
+ case AddressRegisterWithOffsetImmediate:
+ // *($R + #a)
+ addressRegister = context.GetRegister(registerOrMemoryRegion);
+ offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize);
+ sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetImmediate, context);
+ break;
+ case AddressRegisterWithOffsetRegister:
+ // *($R + $r)
+ addressRegister = context.GetRegister(registerOrMemoryRegion);
+ offsetRegister = context.GetRegister(offsetRegisterIndex);
+ sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context);
+ break;
+ case OffsetImmediate:
+ valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16;
+ valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize);
+ sourceOperand = new Value<ulong>(valueImmediate);
+ break;
+ case AddressRegister:
+ // $V
+ sourceOperand = context.GetRegister(registerOrMemoryRegion);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid operand type {operandType} in Atmosphere cheat");
+ }
+
+ return InstructionHelper.CreateCondition(comparison, operationWidth, sourceRegister, sourceOperand);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs
new file mode 100644
index 00000000..02f76e22
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs
@@ -0,0 +1,17 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xFF1 resumes the current process.
+ /// </summary>
+ class ResumeProcess
+ {
+ // FF1?????
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ context.CurrentOperations.Add(new OpProcCtrl(context.Process, false));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs
new file mode 100644
index 00000000..d2e13311
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs
@@ -0,0 +1,65 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xC1 performs saving or restoring of registers.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Save Registers).
+ /// </summary>
+ class SaveOrRestoreRegister
+ {
+ private const int DestinationRegisterIndex = 3;
+ private const int SourceRegisterIndex = 5;
+ private const int OperationTypeIndex = 6;
+
+ private const int RestoreRegister = 0;
+ private const int SaveRegister = 1;
+ private const int ClearSavedValue = 2;
+ private const int ClearRegister = 3;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // C10D0Sx0
+ // D: Destination index.
+ // S: Source index.
+ // x: Operand Type, see below.
+
+ byte destinationRegIndex = instruction[DestinationRegisterIndex];
+ byte sourceRegIndex = instruction[SourceRegisterIndex];
+ byte operationType = instruction[OperationTypeIndex];
+ Impl(operationType, destinationRegIndex, sourceRegIndex, context);
+ }
+
+ public static void Impl(byte operationType, byte destinationRegIndex, byte sourceRegIndex, CompilationContext context)
+ {
+ IOperand destinationOperand;
+ IOperand sourceOperand;
+
+ switch (operationType)
+ {
+ case RestoreRegister:
+ destinationOperand = context.GetRegister(destinationRegIndex);
+ sourceOperand = context.GetSavedRegister(sourceRegIndex);
+ break;
+ case SaveRegister:
+ destinationOperand = context.GetSavedRegister(destinationRegIndex);
+ sourceOperand = context.GetRegister(sourceRegIndex);
+ break;
+ case ClearSavedValue:
+ destinationOperand = new Value<ulong>(0);
+ sourceOperand = context.GetSavedRegister(sourceRegIndex);
+ break;
+ case ClearRegister:
+ destinationOperand = new Value<ulong>(0);
+ sourceOperand = context.GetRegister(sourceRegIndex);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid register operation type {operationType} in Atmosphere cheat");
+ }
+
+ context.CurrentOperations.Add(new OpMov<ulong>(destinationOperand, sourceOperand));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs
new file mode 100644
index 00000000..2264e9d1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0xC2 performs saving or restoring of multiple registers using a bitmask.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Save Registers).
+ /// </summary>
+ class SaveOrRestoreRegisterWithMask
+ {
+ private const int OperationTypeIndex = 2;
+ private const int RegisterMaskIndex = 4;
+
+ private const int RegisterMaskSize = 4;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // C2x0XXXX
+ // x: Operand Type, see below.
+ // X: 16-bit bitmask, bit i == save or restore register i.
+
+ byte operationType = instruction[OperationTypeIndex];
+ ulong mask = InstructionHelper.GetImmediate(instruction, RegisterMaskIndex, RegisterMaskSize);
+
+ for (byte regIndex = 0; mask != 0; mask >>= 1, regIndex++)
+ {
+ if ((mask & 0x1) != 0)
+ {
+ SaveOrRestoreRegister.Impl(operationType, regIndex, regIndex, context);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs
new file mode 100644
index 00000000..1e399b59
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 3 allows for iterating in a loop a fixed number of times.
+ /// </summary>
+ class StartEndLoop
+ {
+ private const int StartOrEndIndex = 1;
+ private const int IterationRegisterIndex = 3;
+ private const int IterationsImmediateIndex = 8;
+
+ private const int IterationsImmediateSize = 8;
+
+ private const byte LoopBegin = 0;
+ private const byte LoopEnd = 1;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 300R0000 VVVVVVVV
+ // R: Register to use as loop counter.
+ // V: Number of iterations to loop.
+
+ // 310R0000
+
+ byte mode = instruction[StartOrEndIndex];
+ byte iterationRegisterIndex = instruction[IterationRegisterIndex];
+
+ switch (mode)
+ {
+ case LoopBegin:
+ // Just start a new compilation block and parse the instruction itself at the end.
+ context.BlockStack.Push(new OperationBlock(instruction));
+ return;
+ case LoopEnd:
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid loop {mode} in Atmosphere cheat");
+ }
+
+ // Use the loop begin instruction stored in the stack.
+ instruction = context.CurrentBlock.BaseInstruction;
+ CodeType codeType = InstructionHelper.GetCodeType(instruction);
+
+ if (codeType != CodeType.StartEndLoop)
+ {
+ throw new TamperCompilationException($"Loop end does not match code type {codeType} in Atmosphere cheat");
+ }
+
+ // Validate if the register in the beginning and end are the same.
+
+ byte oldIterationRegisterIndex = instruction[IterationRegisterIndex];
+
+ if (iterationRegisterIndex != oldIterationRegisterIndex)
+ {
+ throw new TamperCompilationException($"The register used for the loop changed from {oldIterationRegisterIndex} to {iterationRegisterIndex} in Atmosphere cheat");
+ }
+
+ Register iterationRegister = context.GetRegister(iterationRegisterIndex);
+ ulong immediate = InstructionHelper.GetImmediate(instruction, IterationsImmediateIndex, IterationsImmediateSize);
+
+ // Create a loop block with the current operations and nest it in the upper
+ // block of the stack.
+
+ ForBlock block = new ForBlock(immediate, iterationRegister, context.CurrentOperations);
+ context.BlockStack.Pop();
+ context.CurrentOperations.Add(block);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs
new file mode 100644
index 00000000..933646bd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs
@@ -0,0 +1,41 @@
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 0 allows writing a static value to a memory address.
+ /// </summary>
+ class StoreConstantToAddress
+ {
+ private const int OperationWidthIndex = 1;
+ private const int MemoryRegionIndex = 2;
+ private const int OffsetRegisterIndex = 3;
+ private const int OffsetImmediateIndex = 6;
+ private const int ValueImmediateIndex = 16;
+
+ private const int OffsetImmediateSize = 10;
+ private const int ValueImmediateSize8 = 8;
+ private const int ValueImmediateSize16 = 16;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV)
+ // T: Width of memory write(1, 2, 4, or 8 bytes).
+ // M: Memory region to write to(0 = Main NSO, 1 = Heap).
+ // R: Register to use as an offset from memory region base.
+ // A: Immediate offset to use from memory region base.
+ // V: Value to write.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex];
+ Register offsetRegister = context.GetRegister(instruction[OffsetRegisterIndex]);
+ ulong offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize);
+
+ Pointer dstMem = MemoryHelper.EmitPointer(memoryRegion, offsetRegister, offsetImmediate, context);
+
+ int valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16;
+ ulong valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize);
+ Value<ulong> storeValue = new Value<ulong>(valueImmediate);
+
+ InstructionHelper.EmitMov(operationWidth, context, dstMem, storeValue);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs
new file mode 100644
index 00000000..5f036969
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs
@@ -0,0 +1,71 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 6 allows writing a fixed value to a memory address specified by a register.
+ /// </summary>
+ class StoreConstantToMemory
+ {
+ private const int OperationWidthIndex = 1;
+ private const int AddressRegisterIndex = 3;
+ private const int IncrementAddressRegisterIndex = 4;
+ private const int UseOffsetRegisterIndex = 5;
+ private const int OffsetRegisterIndex = 6;
+ private const int ValueImmediateIndex = 8;
+
+ private const int ValueImmediateSize = 16;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // 6T0RIor0 VVVVVVVV VVVVVVVV
+ // T: Width of memory write(1, 2, 4, or 8 bytes).
+ // R: Register used as base memory address.
+ // I: Increment register flag(0 = do not increment R, 1 = increment R by T).
+ // o: Offset register enable flag(0 = do not add r to address, 1 = add r to address).
+ // r: Register used as offset when o is 1.
+ // V: Value to write to memory.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ Register sourceRegister = context.GetRegister(instruction[AddressRegisterIndex]);
+ byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex];
+ byte useOffsetRegister = instruction[UseOffsetRegisterIndex];
+ ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize);
+ Value<ulong> storeValue = new Value<ulong>(immediate);
+
+ Pointer destinationMemory;
+
+ switch (useOffsetRegister)
+ {
+ case 0:
+ // Don't offset the address register by another register.
+ destinationMemory = MemoryHelper.EmitPointer(sourceRegister, context);
+ break;
+ case 1:
+ // Replace the source address by the sum of the base and offset registers.
+ Register offsetRegister = context.GetRegister(instruction[OffsetRegisterIndex]);
+ destinationMemory = MemoryHelper.EmitPointer(sourceRegister, offsetRegister, context);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid offset mode {useOffsetRegister} in Atmosphere cheat");
+ }
+
+ InstructionHelper.EmitMov(operationWidth, context, destinationMemory, storeValue);
+
+ switch (incrementAddressRegister)
+ {
+ case 0:
+ // Don't increment the address register by operationWidth.
+ break;
+ case 1:
+ // Increment the address register by operationWidth.
+ IOperand increment = new Value<ulong>(operationWidth);
+ context.CurrentOperations.Add(new OpAdd<ulong>(sourceRegister, sourceRegister, increment));
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid increment mode {incrementAddressRegister} in Atmosphere cheat");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs
new file mode 100644
index 00000000..422ff298
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs
@@ -0,0 +1,99 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
+{
+ /// <summary>
+ /// Code type 10 allows writing a register to memory.
+ /// </summary>
+ class StoreRegisterToMemory
+ {
+ private const int OperationWidthIndex = 1;
+ private const int SourceRegisterIndex = 2;
+ private const int AddressRegisterIndex = 3;
+ private const int IncrementAddressRegisterIndex = 4;
+ private const int AddressingTypeIndex = 5;
+ private const int RegisterOrMemoryRegionIndex = 6;
+ private const int OffsetImmediateIndex = 7;
+
+ private const int AddressRegister = 0;
+ private const int AddressRegisterWithOffsetRegister = 1;
+ private const int OffsetImmediate = 2;
+ private const int MemoryRegionWithOffsetRegister = 3;
+ private const int MemoryRegionWithOffsetImmediate = 4;
+ private const int MemoryRegionWithOffsetRegisterAndImmediate = 5;
+
+ private const int OffsetImmediateSize1 = 1;
+ private const int OffsetImmediateSize9 = 9;
+
+ public static void Emit(byte[] instruction, CompilationContext context)
+ {
+ // ATSRIOxa (aaaaaaaa)
+ // T: Width of memory write (1, 2, 4, or 8 bytes).
+ // S: Register to write to memory.
+ // R: Register to use as base address.
+ // I: Increment register flag (0 = do not increment R, 1 = increment R by T).
+ // O: Offset type, see below.
+ // x: Register used as offset when O is 1, Memory type when O is 3, 4 or 5.
+ // a: Value used as offset when O is 2, 4 or 5.
+
+ byte operationWidth = instruction[OperationWidthIndex];
+ Register sourceRegister = context.GetRegister(instruction[SourceRegisterIndex]);
+ Register addressRegister = context.GetRegister(instruction[AddressRegisterIndex]);
+ byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex];
+ byte offsetType = instruction[AddressingTypeIndex];
+ byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex];
+ int immediateSize = instruction.Length <= 8 ? OffsetImmediateSize1 : OffsetImmediateSize9;
+ ulong immediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, immediateSize);
+
+ Pointer destinationMemory;
+
+ switch (offsetType)
+ {
+ case AddressRegister:
+ // *($R) = $S
+ destinationMemory = MemoryHelper.EmitPointer(addressRegister, context);
+ break;
+ case AddressRegisterWithOffsetRegister:
+ // *($R + $x) = $S
+ Register offsetRegister = context.GetRegister(registerOrMemoryRegion);
+ destinationMemory = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context);
+ break;
+ case OffsetImmediate:
+ // *(#a) = $S
+ destinationMemory = MemoryHelper.EmitPointer(addressRegister, immediate, context);
+ break;
+ case MemoryRegionWithOffsetRegister:
+ // *(?x + $R) = $S
+ destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, addressRegister, context);
+ break;
+ case MemoryRegionWithOffsetImmediate:
+ // *(?x + #a) = $S
+ destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, immediate, context);
+ break;
+ case MemoryRegionWithOffsetRegisterAndImmediate:
+ // *(?x + #a + $R) = $S
+ destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, addressRegister, immediate, context);
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid offset type {offsetType} in Atmosphere cheat");
+ }
+
+ InstructionHelper.EmitMov(operationWidth, context, destinationMemory, sourceRegister);
+
+ switch (incrementAddressRegister)
+ {
+ case 0:
+ // Don't increment the address register by operationWidth.
+ break;
+ case 1:
+ // Increment the address register by operationWidth.
+ IOperand increment = new Value<ulong>(operationWidth);
+ context.CurrentOperations.Add(new OpAdd<ulong>(addressRegister, addressRegister, increment));
+ break;
+ default:
+ throw new TamperCompilationException($"Invalid increment mode {incrementAddressRegister} in Atmosphere cheat");
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs
new file mode 100644
index 00000000..7c4f2286
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs
@@ -0,0 +1,110 @@
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ /// <summary>
+ /// The opcodes specified for the Atmosphere Cheat VM.
+ /// </summary>
+ enum CodeType
+ {
+ /// <summary>
+ /// Code type 0 allows writing a static value to a memory address.
+ /// </summary>
+ StoreConstantToAddress = 0x0,
+
+ /// <summary>
+ /// Code type 1 performs a comparison of the contents of memory to a static value.
+ /// If the condition is not met, all instructions until the appropriate conditional block terminator
+ /// are skipped.
+ /// </summary>
+ BeginMemoryConditionalBlock = 0x1,
+
+ /// <summary>
+ /// Code type 2 marks the end of a conditional block (started by Code Type 1 or Code Type 8).
+ /// </summary>
+ EndConditionalBlock = 0x2,
+
+ /// <summary>
+ /// Code type 3 allows for iterating in a loop a fixed number of times.
+ /// </summary>
+ StartEndLoop = 0x3,
+
+ /// <summary>
+ /// Code type 4 allows setting a register to a constant value.
+ /// </summary>
+ LoadRegisterWithContant = 0x4,
+
+ /// <summary>
+ /// Code type 5 allows loading a value from memory into a register, either using a fixed address or by
+ /// dereferencing the destination register.
+ /// </summary>
+ LoadRegisterWithMemory = 0x5,
+
+ /// <summary>
+ /// Code type 6 allows writing a fixed value to a memory address specified by a register.
+ /// </summary>
+ StoreConstantToMemory = 0x6,
+
+ /// <summary>
+ /// Code type 7 allows performing arithmetic on registers. However, it has been deprecated by Code
+ /// type 9, and is only kept for backwards compatibility.
+ /// </summary>
+ LegacyArithmetic = 0x7,
+
+ /// <summary>
+ /// Code type 8 enters or skips a conditional block based on whether a key combination is pressed.
+ /// </summary>
+ BeginKeypressConditionalBlock = 0x8,
+
+ /// <summary>
+ /// Code type 9 allows performing arithmetic on registers.
+ /// </summary>
+ Arithmetic = 0x9,
+
+ /// <summary>
+ /// Code type 10 allows writing a register to memory.
+ /// </summary>
+ StoreRegisterToMemory = 0xA,
+
+ /// <summary>
+ /// Code type 0xC0 performs a comparison of the contents of a register and another value.
+ /// This code support multiple operand types, see below. If the condition is not met,
+ /// all instructions until the appropriate conditional block terminator are skipped.
+ /// </summary>
+ BeginRegisterConditionalBlock = 0xC0,
+
+ /// <summary>
+ /// Code type 0xC1 performs saving or restoring of registers.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Save Registers).
+ /// </summary>
+ SaveOrRestoreRegister = 0xC1,
+
+ /// <summary>
+ /// Code type 0xC2 performs saving or restoring of multiple registers using a bitmask.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Save Registers).
+ /// </summary>
+ SaveOrRestoreRegisterWithMask = 0xC2,
+
+ /// <summary>
+ /// Code type 0xC3 reads or writes a static register with a given register.
+ /// NOTE: Registers are saved and restored to a different set of registers than the ones used
+ /// for the other opcodes (Static Registers).
+ /// </summary>
+ ReadOrWriteStaticRegister = 0xC3,
+
+ /// <summary>
+ /// Code type 0xFF0 pauses the current process.
+ /// </summary>
+ PauseProcess = 0xFF0,
+
+ /// <summary>
+ /// Code type 0xFF1 resumes the current process.
+ /// </summary>
+ ResumeProcess = 0xFF1,
+
+ /// <summary>
+ /// Code type 0xFFF writes a debug log.
+ /// </summary>
+ DebugLog = 0xFFF
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs b/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs
new file mode 100644
index 00000000..cd162b1c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ /// <summary>
+ /// The comparisons used by conditional operations.
+ /// </summary>
+ enum Comparison
+ {
+ Greater = 1,
+ GreaterOrEqual = 2,
+ Less = 3,
+ LessOrEqual = 4,
+ Equal = 5,
+ NotEqual = 6
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs b/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs
new file mode 100644
index 00000000..45a47f44
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs
@@ -0,0 +1,75 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class CompilationContext
+ {
+ public OperationBlock CurrentBlock => BlockStack.Peek();
+ public List<IOperation> CurrentOperations => CurrentBlock.Operations;
+
+ public ITamperedProcess Process { get; }
+ public Parameter<long> PressedKeys { get; }
+ public Stack<OperationBlock> BlockStack { get; }
+ public Dictionary<byte, Register> Registers { get; }
+ public Dictionary<byte, Register> SavedRegisters { get; }
+ public Dictionary<byte, Register> StaticRegisters { get; }
+ public ulong ExeAddress { get; }
+ public ulong HeapAddress { get; }
+ public ulong AliasAddress { get; }
+ public ulong AslrAddress { get; }
+
+ public CompilationContext(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process)
+ {
+ Process = process;
+ PressedKeys = new Parameter<long>(0);
+ BlockStack = new Stack<OperationBlock>();
+ Registers = new Dictionary<byte, Register>();
+ SavedRegisters = new Dictionary<byte, Register>();
+ StaticRegisters = new Dictionary<byte, Register>();
+ ExeAddress = exeAddress;
+ HeapAddress = heapAddress;
+ AliasAddress = aliasAddress;
+ AslrAddress = aslrAddress;
+ }
+
+ public Register GetRegister(byte index)
+ {
+ if (Registers.TryGetValue(index, out Register register))
+ {
+ return register;
+ }
+
+ register = new Register($"R_{index:X2}");
+ Registers.Add(index, register);
+
+ return register;
+ }
+
+ public Register GetSavedRegister(byte index)
+ {
+ if (SavedRegisters.TryGetValue(index, out Register register))
+ {
+ return register;
+ }
+
+ register = new Register($"S_{index:X2}");
+ SavedRegisters.Add(index, register);
+
+ return register;
+ }
+
+ public Register GetStaticRegister(byte index)
+ {
+ if (SavedRegisters.TryGetValue(index, out Register register))
+ {
+ return register;
+ }
+
+ register = new Register($"T_{index:X2}");
+ SavedRegisters.Add(index, register);
+
+ return register;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs
new file mode 100644
index 00000000..ad5bd223
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondEQ<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondEQ(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() == (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs
new file mode 100644
index 00000000..d9ad6d81
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondGE<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondGE(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() >= (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs
new file mode 100644
index 00000000..262457da
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondGT<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondGT(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() > (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs
new file mode 100644
index 00000000..fd488bc1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondLE<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondLE(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() <= (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs
new file mode 100644
index 00000000..744eb5dc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondLT<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondLT(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() < (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs
new file mode 100644
index 00000000..2709ad92
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class CondNE<T> : ICondition where T : unmanaged
+ {
+ private IOperand _lhs;
+ private IOperand _rhs;
+
+ public CondNE(IOperand lhs, IOperand rhs)
+ {
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public bool Evaluate()
+ {
+ return (dynamic)_lhs.Get<T>() != (dynamic)_rhs.Get<T>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs
new file mode 100644
index 00000000..f15ceffe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ interface ICondition
+ {
+ bool Evaluate();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs
new file mode 100644
index 00000000..8d75a0e1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Tamper.Conditions
+{
+ class InputMask : ICondition
+ {
+ private long _mask;
+ private Parameter<long> _input;
+
+ public InputMask(long mask, Parameter<long> input)
+ {
+ _mask = mask;
+ _input = input;
+ }
+
+ public bool Evaluate()
+ {
+ return (_input.Value & _mask) == _mask;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs b/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs
new file mode 100644
index 00000000..8458d95d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs
@@ -0,0 +1,13 @@
+using Ryujinx.HLE.HOS.Services.Hid;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ interface ITamperProgram
+ {
+ bool IsEnabled { get; set; }
+ string Name { get; }
+ bool TampersCodeMemory { get; set; }
+ ITamperedProcess Process { get; }
+ void Execute(ControllerKeys pressedKeys);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs
new file mode 100644
index 00000000..c86e1021
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs
@@ -0,0 +1,16 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ interface ITamperedProcess
+ {
+ ProcessState State { get; }
+
+ bool TamperedCodeMemory { get; set; }
+
+ T ReadMemory<T>(ulong va) where T : unmanaged;
+ void WriteMemory<T>(ulong va, T value) where T : unmanaged;
+ void PauseProcess();
+ void ResumeProcess();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs b/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs
new file mode 100644
index 00000000..e85d99c7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs
@@ -0,0 +1,133 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System;
+using System.Globalization;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class InstructionHelper
+ {
+ private const int CodeTypeIndex = 0;
+
+ public static void Emit(IOperation operation, CompilationContext context)
+ {
+ context.CurrentOperations.Add(operation);
+ }
+
+ public static void Emit(Type instruction, byte width, CompilationContext context, params Object[] operands)
+ {
+ Emit((IOperation)Create(instruction, width, operands), context);
+ }
+
+ public static void EmitMov(byte width, CompilationContext context, IOperand destination, IOperand source)
+ {
+ Emit(typeof(OpMov<>), width, context, destination, source);
+ }
+
+ public static ICondition CreateCondition(Comparison comparison, byte width, IOperand lhs, IOperand rhs)
+ {
+ ICondition Create(Type conditionType)
+ {
+ return (ICondition)InstructionHelper.Create(conditionType, width, lhs, rhs);
+ }
+
+ switch (comparison)
+ {
+ case Comparison.Greater : return Create(typeof(CondGT<>));
+ case Comparison.GreaterOrEqual: return Create(typeof(CondGE<>));
+ case Comparison.Less : return Create(typeof(CondLT<>));
+ case Comparison.LessOrEqual : return Create(typeof(CondLE<>));
+ case Comparison.Equal : return Create(typeof(CondEQ<>));
+ case Comparison.NotEqual : return Create(typeof(CondNE<>));
+ default:
+ throw new TamperCompilationException($"Invalid comparison {comparison} in Atmosphere cheat");
+ }
+ }
+
+ public static Object Create(Type instruction, byte width, params Object[] operands)
+ {
+ Type realType;
+
+ switch (width)
+ {
+ case 1: realType = instruction.MakeGenericType(typeof(byte)); break;
+ case 2: realType = instruction.MakeGenericType(typeof(ushort)); break;
+ case 4: realType = instruction.MakeGenericType(typeof(uint)); break;
+ case 8: realType = instruction.MakeGenericType(typeof(ulong)); break;
+ default:
+ throw new TamperCompilationException($"Invalid instruction width {width} in Atmosphere cheat");
+ }
+
+ return Activator.CreateInstance(realType, operands);
+ }
+
+ public static ulong GetImmediate(byte[] instruction, int index, int nybbleCount)
+ {
+ ulong value = 0;
+
+ for (int i = 0; i < nybbleCount; i++)
+ {
+ value <<= 4;
+ value |= instruction[index + i];
+ }
+
+ return value;
+ }
+
+ public static CodeType GetCodeType(byte[] instruction)
+ {
+ int codeType = instruction[CodeTypeIndex];
+
+ if (codeType >= 0xC)
+ {
+ byte extension = instruction[CodeTypeIndex + 1];
+ codeType = (codeType << 4) | extension;
+
+ if (extension == 0xF)
+ {
+ extension = instruction[CodeTypeIndex + 2];
+ codeType = (codeType << 4) | extension;
+ }
+ }
+
+ return (CodeType)codeType;
+ }
+
+ public static byte[] ParseRawInstruction(string rawInstruction)
+ {
+ const int wordSize = 2 * sizeof(uint);
+
+ // Instructions are multi-word, with 32bit words. Split the raw instruction
+ // and parse each word into individual nybbles of bits.
+
+ var words = rawInstruction.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
+
+ byte[] instruction = new byte[wordSize * words.Length];
+
+ if (words.Length == 0)
+ {
+ throw new TamperCompilationException("Empty instruction in Atmosphere cheat");
+ }
+
+ for (int wordIndex = 0; wordIndex < words.Length; wordIndex++)
+ {
+ string word = words[wordIndex];
+
+ if (word.Length != wordSize)
+ {
+ throw new TamperCompilationException($"Invalid word length for {word} in Atmosphere cheat");
+ }
+
+ for (int nybbleIndex = 0; nybbleIndex < wordSize; nybbleIndex++)
+ {
+ int index = wordIndex * wordSize + nybbleIndex;
+
+ instruction[index] = byte.Parse(word.AsSpan(nybbleIndex, 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
+ }
+ }
+
+ return instruction;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs b/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs
new file mode 100644
index 00000000..1260ed9a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs
@@ -0,0 +1,95 @@
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class MemoryHelper
+ {
+ public static ulong GetAddressShift(MemoryRegion source, CompilationContext context)
+ {
+ switch (source)
+ {
+ case MemoryRegion.NSO:
+ // Memory address is relative to the code start.
+ return context.ExeAddress;
+ case MemoryRegion.Heap:
+ // Memory address is relative to the heap.
+ return context.HeapAddress;
+ case MemoryRegion.Alias:
+ // Memory address is relative to the alias region.
+ return context.AliasAddress;
+ case MemoryRegion.Asrl:
+ // Memory address is relative to the asrl region, which matches the code region.
+ return context.AslrAddress;
+ default:
+ throw new TamperCompilationException($"Invalid memory source {source} in Atmosphere cheat");
+ }
+ }
+
+ private static void EmitAdd(Value<ulong> finalValue, IOperand firstOperand, IOperand secondOperand, CompilationContext context)
+ {
+ context.CurrentOperations.Add(new OpAdd<ulong>(finalValue, firstOperand, secondOperand));
+ }
+
+ public static Pointer EmitPointer(ulong addressImmediate, CompilationContext context)
+ {
+ Value<ulong> addressImmediateValue = new Value<ulong>(addressImmediate);
+
+ return new Pointer(addressImmediateValue, context.Process);
+ }
+
+ public static Pointer EmitPointer(Register addressRegister, CompilationContext context)
+ {
+ return new Pointer(addressRegister, context.Process);
+ }
+
+ public static Pointer EmitPointer(Register addressRegister, ulong offsetImmediate, CompilationContext context)
+ {
+ Value<ulong> offsetImmediateValue = new Value<ulong>(offsetImmediate);
+ Value<ulong> finalAddressValue = new Value<ulong>(0);
+ EmitAdd(finalAddressValue, addressRegister, offsetImmediateValue, context);
+
+ return new Pointer(finalAddressValue, context.Process);
+ }
+
+ public static Pointer EmitPointer(Register addressRegister, Register offsetRegister, CompilationContext context)
+ {
+ Value<ulong> finalAddressValue = new Value<ulong>(0);
+ EmitAdd(finalAddressValue, addressRegister, offsetRegister, context);
+
+ return new Pointer(finalAddressValue, context.Process);
+ }
+
+ public static Pointer EmitPointer(Register addressRegister, Register offsetRegister, ulong offsetImmediate, CompilationContext context)
+ {
+ Value<ulong> offsetImmediateValue = new Value<ulong>(offsetImmediate);
+ Value<ulong> finalOffsetValue = new Value<ulong>(0);
+ EmitAdd(finalOffsetValue, offsetRegister, offsetImmediateValue, context);
+ Value<ulong> finalAddressValue = new Value<ulong>(0);
+ EmitAdd(finalAddressValue, addressRegister, finalOffsetValue, context);
+
+ return new Pointer(finalAddressValue, context.Process);
+ }
+
+ public static Pointer EmitPointer(MemoryRegion memoryRegion, ulong offsetImmediate, CompilationContext context)
+ {
+ offsetImmediate += GetAddressShift(memoryRegion, context);
+
+ return EmitPointer(offsetImmediate, context);
+ }
+
+ public static Pointer EmitPointer(MemoryRegion memoryRegion, Register offsetRegister, CompilationContext context)
+ {
+ ulong offsetImmediate = GetAddressShift(memoryRegion, context);
+
+ return EmitPointer(offsetRegister, offsetImmediate, context);
+ }
+
+ public static Pointer EmitPointer(MemoryRegion memoryRegion, Register offsetRegister, ulong offsetImmediate, CompilationContext context)
+ {
+ offsetImmediate += GetAddressShift(memoryRegion, context);
+
+ return EmitPointer(offsetRegister, offsetImmediate, context);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs b/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs
new file mode 100644
index 00000000..766e3eed
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ /// <summary>
+ /// The regions in the virtual address space of the process that are used as base address of memory operations.
+ /// </summary>
+ enum MemoryRegion
+ {
+ /// <summary>
+ /// The position of the NSO associated with the cheat in the virtual address space.
+ /// NOTE: A game can have several NSOs, but the cheat only associates itself with one.
+ /// </summary>
+ NSO = 0x0,
+
+ /// <summary>
+ /// The address of the heap, as determined by the kernel.
+ /// </summary>
+ Heap = 0x1,
+
+ /// <summary>
+ /// The address of the alias region, as determined by the kernel.
+ /// </summary>
+ Alias = 0x2,
+
+ /// <summary>
+ /// The address of the code region with address space layout randomization included.
+ /// </summary>
+ Asrl = 0x3,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs
new file mode 100644
index 00000000..c16c2812
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs
@@ -0,0 +1,17 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ readonly struct OperationBlock
+ {
+ public byte[] BaseInstruction { get; }
+ public List<IOperation> Operations { get; }
+
+ public OperationBlock(byte[] baseInstruction)
+ {
+ BaseInstruction = baseInstruction;
+ Operations = new List<IOperation>();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs
new file mode 100644
index 00000000..d81daa90
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class Block : IOperation
+ {
+ private IEnumerable<IOperation> _operations;
+
+ public Block(IEnumerable<IOperation> operations)
+ {
+ _operations = operations;
+ }
+
+ public Block(params IOperation[] operations)
+ {
+ _operations = operations;
+ }
+
+ public void Execute()
+ {
+ foreach (IOperation op in _operations)
+ {
+ op.Execute();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs
new file mode 100644
index 00000000..ef95fa2b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class ForBlock : IOperation
+ {
+ private ulong _count;
+ private Register _register;
+ private IEnumerable<IOperation> _operations;
+
+ public ForBlock(ulong count, Register register, IEnumerable<IOperation> operations)
+ {
+ _count = count;
+ _register = register;
+ _operations = operations;
+ }
+
+ public ForBlock(ulong count, Register register, params IOperation[] operations)
+ {
+ _count = count;
+ _register = register;
+ _operations = operations;
+ }
+
+ public void Execute()
+ {
+ for (ulong i = 0; i < _count; i++)
+ {
+ // Set the register and execute the operations so that changing the
+ // register during runtime does not break iteration.
+
+ _register.Set<ulong>(i);
+
+ foreach (IOperation op in _operations)
+ {
+ op.Execute();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs
new file mode 100644
index 00000000..1aadda0b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ interface IOperand
+ {
+ public T Get<T>() where T : unmanaged;
+ public void Set<T>(T value) where T : unmanaged;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs
new file mode 100644
index 00000000..a4474979
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ interface IOperation
+ {
+ void Execute();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs
new file mode 100644
index 00000000..b7c5684e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs
@@ -0,0 +1,34 @@
+using Ryujinx.HLE.HOS.Tamper.Conditions;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class IfBlock : IOperation
+ {
+ private ICondition _condition;
+ private IEnumerable<IOperation> _operationsThen;
+ private IEnumerable<IOperation> _operationsElse;
+
+ public IfBlock(ICondition condition, IEnumerable<IOperation> operationsThen, IEnumerable<IOperation> operationsElse)
+ {
+ _condition = condition;
+ _operationsThen = operationsThen;
+ _operationsElse = operationsElse;
+ }
+
+ public void Execute()
+ {
+ IEnumerable<IOperation> operations = _condition.Evaluate() ? _operationsThen : _operationsElse;
+
+ if (operations == null)
+ {
+ return;
+ }
+
+ foreach (IOperation op in operations)
+ {
+ op.Execute();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs
new file mode 100644
index 00000000..214518d7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpAdd<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpAdd(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() + (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs
new file mode 100644
index 00000000..366a82b0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpAnd<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpAnd(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() & (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs
new file mode 100644
index 00000000..49f8b41e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpLog<T> : IOperation where T : unmanaged
+ {
+ int _logId;
+ IOperand _source;
+
+ public OpLog(int logId, IOperand source)
+ {
+ _logId = logId;
+ _source = source;
+ }
+
+ public void Execute()
+ {
+ Logger.Debug?.Print(LogClass.TamperMachine, $"Tamper debug log id={_logId} value={(dynamic)_source.Get<T>():X}");
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs
new file mode 100644
index 00000000..34e7c81a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpLsh<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpLsh(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() << (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs
new file mode 100644
index 00000000..5fad38f9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpMov<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _source;
+
+ public OpMov(IOperand destination, IOperand source)
+ {
+ _destination = destination;
+ _source = source;
+ }
+
+ public void Execute()
+ {
+ _destination.Set(_source.Get<T>());
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs
new file mode 100644
index 00000000..5aa0e34e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpMul<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpMul(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() * (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs
new file mode 100644
index 00000000..8a97c3fe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpNot<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _source;
+
+ public OpNot(IOperand destination, IOperand source)
+ {
+ _destination = destination;
+ _source = source;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)(~(dynamic)_source.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs
new file mode 100644
index 00000000..d074de1c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpOr<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpOr(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() | (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs
new file mode 100644
index 00000000..1b89f450
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs
@@ -0,0 +1,26 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpProcCtrl : IOperation
+ {
+ private ITamperedProcess _process;
+ private bool _pause;
+
+ public OpProcCtrl(ITamperedProcess process, bool pause)
+ {
+ _process = process;
+ _pause = pause;
+ }
+
+ public void Execute()
+ {
+ if (_pause)
+ {
+ _process.PauseProcess();
+ }
+ else
+ {
+ _process.ResumeProcess();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs
new file mode 100644
index 00000000..b08dd957
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpRsh<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpRsh(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() >> (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs
new file mode 100644
index 00000000..b9c67d04
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpSub<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpSub(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() - (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs
new file mode 100644
index 00000000..3bbb76a1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Tamper.Operations
+{
+ class OpXor<T> : IOperation where T : unmanaged
+ {
+ IOperand _destination;
+ IOperand _lhs;
+ IOperand _rhs;
+
+ public OpXor(IOperand destination, IOperand lhs, IOperand rhs)
+ {
+ _destination = destination;
+ _lhs = lhs;
+ _rhs = rhs;
+ }
+
+ public void Execute()
+ {
+ _destination.Set((T)((dynamic)_lhs.Get<T>() ^ (dynamic)_rhs.Get<T>()));
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs b/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs
new file mode 100644
index 00000000..824c62fe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class Parameter<T>
+ {
+ public T Value { get; set; }
+
+ public Parameter(T value)
+ {
+ Value = value;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs b/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs
new file mode 100644
index 00000000..22acf4d5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class Pointer : IOperand
+ {
+ private IOperand _position;
+ private ITamperedProcess _process;
+
+ public Pointer(IOperand position, ITamperedProcess process)
+ {
+ _position = position;
+ _process = process;
+ }
+
+ public T Get<T>() where T : unmanaged
+ {
+ return _process.ReadMemory<T>(_position.Get<ulong>());
+ }
+
+ public void Set<T>(T value) where T : unmanaged
+ {
+ ulong position = _position.Get<ulong>();
+
+ Logger.Debug?.Print(LogClass.TamperMachine, $"0x{position:X16}@{Unsafe.SizeOf<T>()}: {value:X}");
+
+ _process.WriteMemory(position, value);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Register.cs b/src/Ryujinx.HLE/HOS/Tamper/Register.cs
new file mode 100644
index 00000000..01af20de
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Register.cs
@@ -0,0 +1,28 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class Register : IOperand
+ {
+ private ulong _register = 0;
+ private string _alias;
+
+ public Register(string alias)
+ {
+ _alias = alias;
+ }
+
+ public T Get<T>() where T : unmanaged
+ {
+ return (T)(dynamic)_register;
+ }
+
+ public void Set<T>(T value) where T : unmanaged
+ {
+ Logger.Debug?.Print(LogClass.TamperMachine, $"{_alias}: {value}");
+
+ _register = (ulong)(dynamic)value;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs
new file mode 100644
index 00000000..be51264a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs
@@ -0,0 +1,68 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class TamperedKProcess : ITamperedProcess
+ {
+ private KProcess _process;
+
+ public ProcessState State => _process.State;
+
+ public bool TamperedCodeMemory { get; set; } = false;
+
+ public TamperedKProcess(KProcess process)
+ {
+ _process = process;
+ }
+
+ private void AssertMemoryRegion<T>(ulong va, bool isWrite) where T : unmanaged
+ {
+ ulong size = (ulong)Unsafe.SizeOf<T>();
+
+ // TODO (Caian): This double check is workaround because CpuMemory.IsRangeMapped reports
+ // some addresses as mapped even though they are not, i. e. 4 bytes from 0xffffffffffffff70.
+ if (!_process.CpuMemory.IsMapped(va) || !_process.CpuMemory.IsRangeMapped(va, size))
+ {
+ throw new TamperExecutionException($"Unmapped memory access of {size} bytes at 0x{va:X16}");
+ }
+
+ if (!isWrite)
+ {
+ return;
+ }
+
+ // TODO (Caian): The JIT does not support invalidating a code region so writing to code memory may not work
+ // as intended, so taint the operation to issue a warning later.
+ if (isWrite && (va >= _process.MemoryManager.CodeRegionStart) && (va + size <= _process.MemoryManager.CodeRegionEnd))
+ {
+ TamperedCodeMemory = true;
+ }
+ }
+
+ public T ReadMemory<T>(ulong va) where T : unmanaged
+ {
+ AssertMemoryRegion<T>(va, false);
+
+ return _process.CpuMemory.Read<T>(va);
+ }
+
+ public void WriteMemory<T>(ulong va, T value) where T : unmanaged
+ {
+ AssertMemoryRegion<T>(va, true);
+ _process.CpuMemory.Write(va, value);
+ }
+
+ public void PauseProcess()
+ {
+ Logger.Warning?.Print(LogClass.TamperMachine, "Process pausing is not supported!");
+ }
+
+ public void ResumeProcess()
+ {
+ Logger.Warning?.Print(LogClass.TamperMachine, "Process resuming is not supported!");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Tamper/Value.cs b/src/Ryujinx.HLE/HOS/Tamper/Value.cs
new file mode 100644
index 00000000..865f8e04
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Tamper/Value.cs
@@ -0,0 +1,24 @@
+using Ryujinx.HLE.HOS.Tamper.Operations;
+
+namespace Ryujinx.HLE.HOS.Tamper
+{
+ class Value<P> : IOperand where P : unmanaged
+ {
+ private P _value;
+
+ public Value(P value)
+ {
+ _value = value;
+ }
+
+ public T Get<T>() where T : unmanaged
+ {
+ return (T)(dynamic)_value;
+ }
+
+ public void Set<T>(T value) where T : unmanaged
+ {
+ _value = (P)(dynamic)value;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/TamperMachine.cs b/src/Ryujinx.HLE/HOS/TamperMachine.cs
new file mode 100644
index 00000000..596fc3ce
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/TamperMachine.cs
@@ -0,0 +1,186 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Tamper;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class TamperMachine
+ {
+ // Atmosphere specifies a delay of 83 milliseconds between the execution of the last
+ // cheat and the re-execution of the first one.
+ private const int TamperMachineSleepMs = 1000 / 12;
+
+ private Thread _tamperThread = null;
+ private ConcurrentQueue<ITamperProgram> _programs = new ConcurrentQueue<ITamperProgram>();
+ private long _pressedKeys = 0;
+ private Dictionary<string, ITamperProgram> _programDictionary = new Dictionary<string, ITamperProgram>();
+
+ private void Activate()
+ {
+ if (_tamperThread == null || !_tamperThread.IsAlive)
+ {
+ _tamperThread = new Thread(this.TamperRunner);
+ _tamperThread.Name = "HLE.TamperMachine";
+ _tamperThread.Start();
+ }
+ }
+
+ internal void InstallAtmosphereCheat(string name, string buildId, IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress)
+ {
+ if (!CanInstallOnPid(info.Process.Pid))
+ {
+ return;
+ }
+
+ ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process);
+ AtmosphereCompiler compiler = new AtmosphereCompiler(exeAddress, info.HeapAddress, info.AliasAddress, info.AslrAddress, tamperedProcess);
+ ITamperProgram program = compiler.Compile(name, rawInstructions);
+
+ if (program != null)
+ {
+ program.TampersCodeMemory = false;
+
+ _programs.Enqueue(program);
+ _programDictionary.TryAdd($"{buildId}-{name}", program);
+ }
+
+ Activate();
+ }
+
+ private bool CanInstallOnPid(ulong pid)
+ {
+ // Do not allow tampering of kernel processes.
+ if (pid < KernelConstants.InitialProcessId)
+ {
+ Logger.Warning?.Print(LogClass.TamperMachine, $"Refusing to tamper kernel process {pid}");
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public void EnableCheats(string[] enabledCheats)
+ {
+ foreach (var program in _programDictionary.Values)
+ {
+ program.IsEnabled = false;
+ }
+
+ foreach (var cheat in enabledCheats)
+ {
+ if (_programDictionary.TryGetValue(cheat, out var program))
+ {
+ program.IsEnabled = true;
+ }
+ }
+ }
+
+ private bool IsProcessValid(ITamperedProcess process)
+ {
+ return process.State != ProcessState.Crashed && process.State != ProcessState.Exiting && process.State != ProcessState.Exited;
+ }
+
+ private void TamperRunner()
+ {
+ Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread running");
+
+ int sleepCounter = 0;
+
+ while (true)
+ {
+ // Sleep to not consume too much CPU.
+ if (sleepCounter == 0)
+ {
+ sleepCounter = _programs.Count;
+ Thread.Sleep(TamperMachineSleepMs);
+ }
+ else
+ {
+ sleepCounter--;
+ }
+
+ if (!AdvanceTamperingsQueue())
+ {
+ // No more work to be done.
+
+ Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread exiting");
+
+ return;
+ }
+ }
+ }
+
+ private bool AdvanceTamperingsQueue()
+ {
+ if (!_programs.TryDequeue(out ITamperProgram program))
+ {
+ // No more programs in the queue.
+ _programDictionary.Clear();
+
+ return false;
+ }
+
+ // Check if the process is still suitable for running the tamper program.
+ if (!IsProcessValid(program.Process))
+ {
+ // Exit without re-enqueuing the program because the process is no longer valid.
+ return true;
+ }
+
+ // Re-enqueue the tampering program because the process is still valid.
+ _programs.Enqueue(program);
+
+ Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}");
+
+ try
+ {
+ ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys);
+ program.Process.TamperedCodeMemory = false;
+ program.Execute(pressedKeys);
+
+ // Detect the first attempt to tamper memory and log it.
+ if (!program.TampersCodeMemory && program.Process.TamperedCodeMemory)
+ {
+ program.TampersCodeMemory = true;
+
+ Logger.Warning?.Print(LogClass.TamperMachine, $"Tampering program {program.Name} modifies code memory so it may not work properly");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Debug?.Print(LogClass.TamperMachine, $"The tampering program {program.Name} crashed, this can happen while the game is starting");
+
+ if (!string.IsNullOrEmpty(ex.Message))
+ {
+ Logger.Debug?.Print(LogClass.TamperMachine, ex.Message);
+ }
+ }
+
+ return true;
+ }
+
+ public void UpdateInput(List<GamepadInput> gamepadInputs)
+ {
+ // Look for the input of the player one or the handheld.
+ foreach (GamepadInput input in gamepadInputs)
+ {
+ if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld)
+ {
+ Thread.VolatileWrite(ref _pressedKeys, (long)input.Buttons);
+
+ return;
+ }
+ }
+
+ // Clear the input because player one is not conected.
+ Thread.VolatileWrite(ref _pressedKeys, 0);
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs b/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs
new file mode 100644
index 00000000..4b041ce8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs
@@ -0,0 +1,60 @@
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class UserChannelPersistence
+ {
+ private Stack<byte[]> _userChannelStorages;
+ public int PreviousIndex { get; private set; }
+ public int Index { get; private set; }
+ public ProgramSpecifyKind Kind { get; private set; }
+ public bool ShouldRestart { get; set; }
+
+ public UserChannelPersistence()
+ {
+ _userChannelStorages = new Stack<byte[]>();
+ Kind = ProgramSpecifyKind.ExecuteProgram;
+ PreviousIndex = -1;
+ Index = 0;
+ }
+
+ public void Clear()
+ {
+ _userChannelStorages.Clear();
+ }
+
+ public void Push(byte[] data)
+ {
+ _userChannelStorages.Push(data);
+ }
+
+ public byte[] Pop()
+ {
+ _userChannelStorages.TryPop(out byte[] result);
+
+ return result;
+ }
+
+ public bool IsEmpty => _userChannelStorages.Count == 0;
+
+ public void ExecuteProgram(ProgramSpecifyKind kind, ulong value)
+ {
+ Kind = kind;
+ PreviousIndex = Index;
+ ShouldRestart = true;
+
+ switch (kind)
+ {
+ case ProgramSpecifyKind.ExecuteProgram:
+ Index = (int)value;
+ break;
+ case ProgramSpecifyKind.RestartProgram:
+ break;
+ default:
+ throw new NotImplementedException($"{kind} not implemented");
+ }
+ }
+ }
+}