aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-08-16 20:47:36 -0300
committerGitHub <noreply@github.com>2018-08-16 20:47:36 -0300
commit521751795a1c97c0d97f6f8904a3be69b13d3a9d (patch)
tree942a05899c40e2de6d92a38b93a494bd96ee64b8 /Ryujinx.HLE/HOS
parent182d716867ae477c2b15a5332430dc2641fa1cc3 (diff)
Code style fixes and nits on the HLE project (#355)
* Some style fixes and nits on ITimeZoneService * Remove some unneeded usings * Remove the Ryujinx.HLE.OsHle.Handles namespace * Remove hbmenu automatic load on process exit * Rename Ns to Device, rename Os to System, rename SystemState to State * Move Exceptions and Utilities out of OsHle * Rename OsHle to HOS * Rename OsHle folder to HOS * IManagerDisplayService and ISystemDisplayService style fixes * BsdError shouldn't be public * Add a empty new line before using static * Remove unused file * Some style fixes on NPDM * Exit gracefully when the application is closed * Code style fixes on IGeneralService * Add 0x prefix on values printed as hex * Small improvements on finalization code * Move ProcessId and ThreadId out of AThreadState * Rename VFs to FileSystem * FsAccessHeader shouldn't be public. Also fix file names casing * More case changes on NPDM * Remove unused files * Move using to the correct place on NPDM * Use properties on KernelAccessControlMmio * Address PR feedback
Diffstat (limited to 'Ryujinx.HLE/HOS')
-rw-r--r--Ryujinx.HLE/HOS/Diagnostics/Demangler.cs416
-rw-r--r--Ryujinx.HLE/HOS/ErrorCode.cs10
-rw-r--r--Ryujinx.HLE/HOS/ErrorModule.cs101
-rw-r--r--Ryujinx.HLE/HOS/Font/SharedFontManager.cs122
-rw-r--r--Ryujinx.HLE/HOS/Font/SharedFontType.cs13
-rw-r--r--Ryujinx.HLE/HOS/GlobalStateTable.cs69
-rw-r--r--Ryujinx.HLE/HOS/Homebrew.cs77
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs227
-rw-r--r--Ryujinx.HLE/HOS/IdDictionary.cs73
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs27
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs92
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcHandler.cs140
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcMagic.cs8
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcMessage.cs215
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs12
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs26
-rw-r--r--Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs19
-rw-r--r--Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs4
-rw-r--r--Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs111
-rw-r--r--Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs10
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KEvent.cs4
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs43
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs33
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs1081
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs34
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs370
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSession.cs31
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs14
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs28
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KThread.cs98
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs60
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs14
-rw-r--r--Ryujinx.HLE/HOS/Kernel/KernelErr.cs22
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs22
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs18
-rw-r--r--Ryujinx.HLE/HOS/Kernel/MemoryState.cs49
-rw-r--r--Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs19
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs48
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcHandler.cs123
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcMemory.cs577
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcSystem.cs373
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcThread.cs385
-rw-r--r--Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs523
-rw-r--r--Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs158
-rw-r--r--Ryujinx.HLE/HOS/Process.cs438
-rw-r--r--Ryujinx.HLE/HOS/ServiceCtx.cs39
-rw-r--r--Ryujinx.HLE/HOS/Services/Acc/AccErr.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs125
-rw-r--r--Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs38
-rw-r--r--Ryujinx.HLE/HOS/Services/Acc/IProfile.cs58
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AmErr.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/FocusState.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IAllSystemAppletProxiesService.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs117
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs83
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IAudioController.cs72
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs115
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs46
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs71
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs37
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ISelfController.cs145
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IStorage.cs31
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs83
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs99
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/IWindowController.cs38
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/OperationMode.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Apm/IManager.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Apm/ISession.cs41
-rw-r--r--Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs18
-rw-r--r--Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudErr.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs163
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/AudioConsts.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs16
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs318
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolContext.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolState.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs191
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/UpdateDataHeader.cs22
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceChannelResourceIn.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs188
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs49
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs22
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs223
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs170
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs169
-rw-r--r--Ryujinx.HLE/HOS/Services/Aud/SampleFormat.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Bcat/IBcatService.cs21
-rw-r--r--Ryujinx.HLE/HOS/Services/Bcat/IDeliveryCacheStorageService.cs21
-rw-r--r--Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs39
-rw-r--r--Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs18
-rw-r--r--Ryujinx.HLE/HOS/Services/Bsd/IClient.cs445
-rw-r--r--Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs49
-rw-r--r--Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs116
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs110
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs412
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs74
-rw-r--r--Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs51
-rw-r--r--Ryujinx.HLE/HOS/Services/Hid/IActiveVibrationDeviceList.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs34
-rw-r--r--Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs299
-rw-r--r--Ryujinx.HLE/HOS/Services/IIpcService.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/IpcService.cs183
-rw-r--r--Ryujinx.HLE/HOS/Services/Lm/ILogService.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Lm/ILogger.cs86
-rw-r--r--Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Lm/LmLogLevel.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Mm/IRequest.cs46
-rw-r--r--Ryujinx.HLE/HOS/Services/Nfp/DeviceState.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Nfp/IUser.cs114
-rw-r--r--Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Nfp/State.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs58
-rw-r--r--Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs95
-rw-r--r--Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs35
-rw-r--r--Ryujinx.HLE/HOS/Services/Nifm/NifmErr.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs42
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs231
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvFd.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs197
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs329
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASRemap.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs43
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs187
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs16
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHelper.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannel.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannelName.cs7
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs210
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs398
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs19
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEvent.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEventState.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostSyncPt.cs107
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapAlloc.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapCreate.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFree.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFromId.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapGetId.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandle.cs37
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandleParam.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs302
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapParam.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Nv/NvResult.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs41
-rw-r--r--Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs35
-rw-r--r--Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs125
-rw-r--r--Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs28
-rw-r--r--Ryujinx.HLE/HOS/Services/ServiceFactory.cs181
-rw-r--r--Ryujinx.HLE/HOS/Services/Set/ISettingsServer.cs79
-rw-r--r--Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs151
-rw-r--r--Ryujinx.HLE/HOS/Services/Set/NxSettings.cs1711
-rw-r--r--Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs69
-rw-r--r--Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs50
-rw-r--r--Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs30
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/IStaticService.cs74
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs53
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs107
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs280
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/Display.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/GbpBuffer.cs60
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs233
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs29
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs100
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs54
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs29
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs48
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs29
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs416
-rw-r--r--Ryujinx.HLE/HOS/Services/Vi/Parcel.cs58
-rw-r--r--Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs62
-rw-r--r--Ryujinx.HLE/HOS/SystemState/ColorSet.cs8
-rw-r--r--Ryujinx.HLE/HOS/SystemState/OpenCloseState.cs8
-rw-r--r--Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs23
-rw-r--r--Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs145
-rw-r--r--Ryujinx.HLE/HOS/SystemState/UserId.cs76
-rw-r--r--Ryujinx.HLE/HOS/SystemState/UserProfile.cs36
211 files changed, 18936 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs b/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs
new file mode 100644
index 00000000..aea979c2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Diagnostics/Demangler.cs
@@ -0,0 +1,416 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Diagnostics
+{
+ static class Demangler
+ {
+ private static readonly Dictionary<string, string> BuiltinTypes = new Dictionary<string, string>
+ {
+ { "v", "void" },
+ { "w", "wchar_t" },
+ { "b", "bool" },
+ { "c", "char" },
+ { "a", "signed char" },
+ { "h", "unsigned char" },
+ { "s", "short" },
+ { "t", "unsigned short" },
+ { "i", "int" },
+ { "j", "unsigned int" },
+ { "l", "long" },
+ { "m", "unsigned long" },
+ { "x", "long long" },
+ { "y", "unsigned long long" },
+ { "n", "__int128" },
+ { "o", "unsigned __int128" },
+ { "f", "float" },
+ { "d", "double" },
+ { "e", "long double" },
+ { "g", "__float128" },
+ { "z", "..." },
+ { "Dd", "__iec559_double" },
+ { "De", "__iec559_float128" },
+ { "Df", "__iec559_float" },
+ { "Dh", "__iec559_float16" },
+ { "Di", "char32_t" },
+ { "Ds", "char16_t" },
+ { "Da", "decltype(auto)" },
+ { "Dn", "std::nullptr_t" },
+ };
+
+ private static readonly Dictionary<string, string> SubstitutionExtra = new Dictionary<string, string>
+ {
+ {"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 static int FromBase36(string encoded)
+ {
+ string base36 = "0123456789abcdefghijklmnopqrstuvwxyz";
+ char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray();
+ int result = 0;
+ for (int i = 0; i < reversedEncoded.Length; i++)
+ {
+ char c = reversedEncoded[i];
+ int value = base36.IndexOf(c);
+ if (value == -1)
+ return -1;
+ result += value * (int)Math.Pow(36, i);
+ }
+ return result;
+ }
+
+ private static string GetCompressedValue(string compression, List<string> compressionData, out int pos)
+ {
+ string res = null;
+ bool canHaveUnqualifiedName = false;
+ pos = -1;
+ if (compressionData.Count == 0 || !compression.StartsWith("S"))
+ return null;
+
+ if (compression.Length >= 2 && SubstitutionExtra.TryGetValue(compression.Substring(0, 2), out string substitutionValue))
+ {
+ pos = 1;
+ res = substitutionValue;
+ compression = compression.Substring(2);
+ }
+ else if (compression.StartsWith("St"))
+ {
+ pos = 1;
+ canHaveUnqualifiedName = true;
+ res = "std";
+ compression = compression.Substring(2);
+ }
+ else if (compression.StartsWith("S_"))
+ {
+ pos = 1;
+ res = compressionData[0];
+ canHaveUnqualifiedName = true;
+ compression = compression.Substring(2);
+ }
+ else
+ {
+ int id = -1;
+ int underscorePos = compression.IndexOf('_');
+ if (underscorePos == -1)
+ return null;
+ string partialId = compression.Substring(1, underscorePos - 1);
+
+ id = FromBase36(partialId);
+ if (id == -1 || compressionData.Count <= (id + 1))
+ {
+ return null;
+ }
+ res = compressionData[id + 1];
+ pos = partialId.Length + 1;
+ canHaveUnqualifiedName= true;
+ compression = compression.Substring(pos);
+ }
+ if (res != null)
+ {
+ if (canHaveUnqualifiedName)
+ {
+ List<string> type = ReadName(compression, compressionData, out int endOfNameType);
+ if (endOfNameType != -1 && type != null)
+ {
+ pos += endOfNameType;
+ res = res + "::" + type[type.Count - 1];
+ }
+ }
+ }
+ return res;
+ }
+
+ private static List<string> ReadName(string mangled, List<string> compressionData, out int pos, bool isNested = true)
+ {
+ List<string> res = new List<string>();
+ string charCountString = null;
+ int charCount = 0;
+ int i;
+
+ pos = -1;
+ for (i = 0; i < mangled.Length; i++)
+ {
+ char chr = mangled[i];
+ if (charCountString == null)
+ {
+ if (ReadCVQualifiers(chr) != null)
+ {
+ continue;
+ }
+ if (chr == 'S')
+ {
+ string data = GetCompressedValue(mangled.Substring(i), compressionData, out pos);
+ if (pos == -1)
+ {
+ return null;
+ }
+ if (res.Count == 0)
+ res.Add(data);
+ else
+ res.Add(res[res.Count - 1] + "::" + data);
+ i += pos;
+ if (i < mangled.Length && mangled[i] == 'E')
+ {
+ break;
+ }
+ continue;
+ }
+ else if (chr == 'E')
+ {
+ break;
+ }
+ }
+ if (Char.IsDigit(chr))
+ {
+ charCountString += chr;
+ }
+ else
+ {
+ if (!int.TryParse(charCountString, out charCount))
+ {
+ return null;
+ }
+ string demangledPart = mangled.Substring(i, charCount);
+ if (res.Count == 0)
+ res.Add(demangledPart);
+ else
+ res.Add(res[res.Count - 1] + "::" + demangledPart);
+ i = i + charCount - 1;
+ charCount = 0;
+ charCountString = null;
+ if (!isNested)
+ break;
+ }
+ }
+ if (res.Count == 0)
+ {
+ return null;
+ }
+ pos = i;
+ return res;
+ }
+
+ private static string ReadBuiltinType(string mangledType, out int pos)
+ {
+ string res = null;
+ string possibleBuiltinType;
+ pos = -1;
+ possibleBuiltinType = mangledType[0].ToString();
+ if (!BuiltinTypes.TryGetValue(possibleBuiltinType, out res))
+ {
+ if (mangledType.Length >= 2)
+ {
+ // Try to match the first 2 chars if the first call failed
+ possibleBuiltinType = mangledType.Substring(0, 2);
+ BuiltinTypes.TryGetValue(possibleBuiltinType, out res);
+ }
+ }
+ if (res != null)
+ pos = possibleBuiltinType.Length;
+ return res;
+ }
+
+ private static string ReadCVQualifiers(char qualifier)
+ {
+ if (qualifier == 'r')
+ return "restricted";
+ else if (qualifier == 'V')
+ return "volatile";
+ else if (qualifier == 'K')
+ return "const";
+ return null;
+ }
+
+ private static string ReadRefQualifiers(char qualifier)
+ {
+ if (qualifier == 'R')
+ return "&";
+ else if (qualifier == 'O')
+ return "&&";
+ return null;
+ }
+
+ private static string ReadSpecialQualifiers(char qualifier)
+ {
+ if (qualifier == 'P')
+ return "*";
+ else if (qualifier == 'C')
+ return "complex";
+ else if (qualifier == 'G')
+ return "imaginary";
+ return null;
+ }
+
+ private static List<string> ReadParameters(string mangledParams, List<string> compressionData, out int pos)
+ {
+ List<string> res = new List<string>();
+ List<string> refQualifiers = new List<string>();
+ string parsedTypePart = null;
+ string currentRefQualifiers = null;
+ string currentBuiltinType = null;
+ string currentSpecialQualifiers = null;
+ string currentCompressedValue = null;
+ int i = 0;
+ pos = -1;
+
+ for (i = 0; i < mangledParams.Length; i++)
+ {
+ if (currentBuiltinType != null)
+ {
+ string currentCVQualifier = String.Join(" ", refQualifiers);
+ // Try to mimic the compression indexing
+ if (currentRefQualifiers != null)
+ {
+ compressionData.Add(currentBuiltinType + currentRefQualifiers);
+ }
+ if (refQualifiers.Count != 0)
+ {
+ compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers);
+ }
+ if (currentSpecialQualifiers != null)
+ {
+ compressionData.Add(currentBuiltinType + " " + currentCVQualifier + currentRefQualifiers + currentSpecialQualifiers);
+ }
+ if (currentRefQualifiers == null && currentCVQualifier == null && currentSpecialQualifiers == null)
+ {
+ compressionData.Add(currentBuiltinType);
+ }
+ currentBuiltinType = null;
+ currentCompressedValue = null;
+ currentCVQualifier = null;
+ currentRefQualifiers = null;
+ refQualifiers.Clear();
+ currentSpecialQualifiers = null;
+ }
+ char chr = mangledParams[i];
+ string part = mangledParams.Substring(i);
+
+ // Try to read qualifiers
+ parsedTypePart = ReadCVQualifiers(chr);
+ if (parsedTypePart != null)
+ {
+ refQualifiers.Add(parsedTypePart);
+
+ // need more data
+ continue;
+ }
+
+ parsedTypePart = ReadRefQualifiers(chr);
+ if (parsedTypePart != null)
+ {
+ currentRefQualifiers = parsedTypePart;
+
+ // need more data
+ continue;
+ }
+
+ parsedTypePart = ReadSpecialQualifiers(chr);
+ if (parsedTypePart != null)
+ {
+ currentSpecialQualifiers = parsedTypePart;
+
+ // need more data
+ continue;
+ }
+
+ // TODO: extended-qualifier?
+
+ if (part.StartsWith("S"))
+ {
+ parsedTypePart = GetCompressedValue(part, compressionData, out pos);
+ if (pos != -1 && parsedTypePart != null)
+ {
+ currentCompressedValue = parsedTypePart;
+ i += pos;
+ res.Add(currentCompressedValue + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
+ currentBuiltinType = null;
+ currentCompressedValue = null;
+ currentRefQualifiers = null;
+ refQualifiers.Clear();
+ currentSpecialQualifiers = null;
+ continue;
+ }
+ pos = -1;
+ return null;
+ }
+ else if (part.StartsWith("N"))
+ {
+ part = part.Substring(1);
+ List<string> name = ReadName(part, compressionData, out pos);
+ if (pos != -1 && name != null)
+ {
+ i += pos + 1;
+ res.Add(name[name.Count - 1] + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
+ currentBuiltinType = null;
+ currentCompressedValue = null;
+ currentRefQualifiers = null;
+ refQualifiers.Clear();
+ currentSpecialQualifiers = null;
+ continue;
+ }
+ }
+
+ // Try builting
+ parsedTypePart = ReadBuiltinType(part, out pos);
+ if (pos == -1)
+ {
+ return null;
+ }
+ currentBuiltinType = parsedTypePart;
+ res.Add(currentBuiltinType + " " + String.Join(" ", refQualifiers) + currentRefQualifiers + currentSpecialQualifiers);
+ i = i + pos -1;
+ }
+ pos = i;
+ return res;
+ }
+
+ private static string ParseFunctionName(string mangled)
+ {
+ List<string> compressionData = new List<string>();
+ int pos = 0;
+ string res;
+ bool isNested = mangled.StartsWith("N");
+
+ // If it's start with "N" it must be a nested function name
+ if (isNested)
+ mangled = mangled.Substring(1);
+ compressionData = ReadName(mangled, compressionData, out pos, isNested);
+ if (pos == -1)
+ return null;
+ res = compressionData[compressionData.Count - 1];
+ compressionData.Remove(res);
+ mangled = mangled.Substring(pos + 1);
+
+ // more data? maybe not a data name so...
+ if (mangled != String.Empty)
+ {
+ List<string> parameters = ReadParameters(mangled, compressionData, out pos);
+ // parameters parsing error, we return the original data to avoid information loss.
+ if (pos == -1)
+ return null;
+ parameters = parameters.Select(outer => outer.Trim()).ToList();
+ res += "(" + String.Join(", ", parameters) + ")";
+ }
+ return res;
+ }
+
+ public static string Parse(string originalMangled)
+ {
+ if (originalMangled.StartsWith("_Z"))
+ {
+ // We assume that we have a name (TOOD: support special names)
+ string res = ParseFunctionName(originalMangled.Substring(2));
+ if (res == null)
+ return originalMangled;
+ return res;
+ }
+ return originalMangled;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ErrorCode.cs b/Ryujinx.HLE/HOS/ErrorCode.cs
new file mode 100644
index 00000000..767664f8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/ErrorCode.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS
+{
+ static class ErrorCode
+ {
+ public static uint MakeError(ErrorModule Module, int Code)
+ {
+ return (uint)Module | ((uint)Code << 9);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ErrorModule.cs b/Ryujinx.HLE/HOS/ErrorModule.cs
new file mode 100644
index 00000000..5c6c9b05
--- /dev/null
+++ b/Ryujinx.HLE/HOS/ErrorModule.cs
@@ -0,0 +1,101 @@
+namespace Ryujinx.HLE.HOS
+{
+ enum ErrorModule
+ {
+ Kernel = 1,
+ Fs = 2,
+ Os = 3, // (Memory, Thread, Mutex, NVIDIA)
+ Htcs = 4,
+ Ncm = 5,
+ Dd = 6,
+ Debug_Monitor = 7,
+ Lr = 8,
+ Loader = 9,
+ IPC_Command_Interface = 10,
+ IPC = 11,
+ Pm = 15,
+ Ns = 16,
+ Socket = 17,
+ Htc = 18,
+ Ncm_Content = 20,
+ Sm = 21,
+ RO_Userland = 22,
+ SdMmc = 24,
+ Ovln = 25,
+ Spl = 26,
+ Ethc = 100,
+ I2C = 101,
+ Gpio = 102,
+ Uart = 103,
+ Settings = 105,
+ Wlan = 107,
+ Xcd = 108,
+ Nifm = 110,
+ Hwopus = 111,
+ Bluetooth = 113,
+ Vi = 114,
+ Nfp = 115,
+ Time = 116,
+ Fgm = 117,
+ Oe = 118,
+ Pcie = 120,
+ Friends = 121,
+ Bcat = 122,
+ SSL = 123,
+ Account = 124,
+ News = 125,
+ Mii = 126,
+ Nfc = 127,
+ Am = 128,
+ Play_Report = 129,
+ Ahid = 130,
+ Qlaunch = 132,
+ Pcv = 133,
+ Omm = 134,
+ Bpc = 135,
+ Psm = 136,
+ Nim = 137,
+ Psc = 138,
+ Tc = 139,
+ Usb = 140,
+ Nsd = 141,
+ Pctl = 142,
+ Btm = 143,
+ Ec = 144,
+ ETicket = 145,
+ Ngc = 146,
+ Error_Report = 147,
+ Apm = 148,
+ Profiler = 150,
+ Error_Upload = 151,
+ Audio = 153,
+ Npns = 154,
+ Npns_Http_Stream = 155,
+ Arp = 157,
+ Swkbd = 158,
+ Boot = 159,
+ Nfc_Mifare = 161,
+ Userland_Assert = 162,
+ Fatal = 163,
+ Nim_Shop = 164,
+ Spsm = 165,
+ Bgtc = 167,
+ Userland_Crash = 168,
+ SRepo = 180,
+ Dauth = 181,
+ Hid = 202,
+ Ldn = 203,
+ Irsensor = 205,
+ Capture = 206,
+ Manu = 208,
+ Atk = 209,
+ Web = 210,
+ Grc = 212,
+ Migration = 216,
+ Migration_Ldc_Server = 217,
+ General_Web_Applet = 800,
+ Wifi_Web_Auth_Applet = 809,
+ Whitelisted_Applet = 810,
+ ShopN = 811
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
new file mode 100644
index 00000000..0be5e896
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs
@@ -0,0 +1,122 @@
+using Ryujinx.HLE.Memory;
+using Ryujinx.HLE.Resource;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Font
+{
+ class SharedFontManager
+ {
+ private DeviceMemory Memory;
+
+ private long PhysicalAddress;
+
+ private string FontsPath;
+
+ private struct FontInfo
+ {
+ public int Offset;
+ public int Size;
+
+ public FontInfo(int Offset, int Size)
+ {
+ this.Offset = Offset;
+ this.Size = Size;
+ }
+ }
+
+ private Dictionary<SharedFontType, FontInfo> FontData;
+
+ public SharedFontManager(Switch Device, long PhysicalAddress)
+ {
+ this.PhysicalAddress = PhysicalAddress;
+
+ Memory = Device.Memory;
+
+ FontsPath = Path.Combine(Device.FileSystem.GetSystemPath(), "fonts");
+ }
+
+ public void EnsureInitialized()
+ {
+ if (FontData == null)
+ {
+ Memory.FillWithZeros(PhysicalAddress, Horizon.FontSize);
+
+ uint FontOffset = 0;
+
+ FontInfo CreateFont(string Name)
+ {
+ string FontFilePath = Path.Combine(FontsPath, Name + ".ttf");
+
+ if (File.Exists(FontFilePath))
+ {
+ byte[] Data = File.ReadAllBytes(FontFilePath);
+
+ FontInfo Info = new FontInfo((int)FontOffset, Data.Length);
+
+ WriteMagicAndSize(PhysicalAddress + FontOffset, Data.Length);
+
+ FontOffset += 8;
+
+ uint Start = FontOffset;
+
+ for (; FontOffset - Start < Data.Length; FontOffset++)
+ {
+ Memory.WriteByte(PhysicalAddress + FontOffset, Data[FontOffset - Start]);
+ }
+
+ return Info;
+ }
+ else
+ {
+ throw new InvalidSystemResourceException($"Font \"{Name}.ttf\" not found. Please provide it in \"{FontsPath}\".");
+ }
+ }
+
+ 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(long Position, int Size)
+ {
+ const int DecMagic = 0x18029a7f;
+ const int Key = 0x49621806;
+
+ int EncryptedSize = EndianSwap.Swap32(Size ^ Key);
+
+ Memory.WriteInt32(Position + 0, DecMagic);
+ Memory.WriteInt32(Position + 4, EncryptedSize);
+ }
+
+ public int GetFontSize(SharedFontType FontType)
+ {
+ EnsureInitialized();
+
+ return FontData[FontType].Size;
+ }
+
+ public int GetSharedMemoryAddressOffset(SharedFontType FontType)
+ {
+ EnsureInitialized();
+
+ return FontData[FontType].Offset + 8;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Font/SharedFontType.cs b/Ryujinx.HLE/HOS/Font/SharedFontType.cs
new file mode 100644
index 00000000..53dca626
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Font/SharedFontType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Font
+{
+ 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/Ryujinx.HLE/HOS/GlobalStateTable.cs b/Ryujinx.HLE/HOS/GlobalStateTable.cs
new file mode 100644
index 00000000..faf47b2e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/GlobalStateTable.cs
@@ -0,0 +1,69 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS
+{
+ class GlobalStateTable
+ {
+ private ConcurrentDictionary<Process, IdDictionary> DictByProcess;
+
+ public GlobalStateTable()
+ {
+ DictByProcess = new ConcurrentDictionary<Process, IdDictionary>();
+ }
+
+ public bool Add(Process Process, int Id, object Data)
+ {
+ IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
+
+ return Dict.Add(Id, Data);
+ }
+
+ public int Add(Process Process, object Data)
+ {
+ IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary());
+
+ return Dict.Add(Data);
+ }
+
+ public object GetData(Process Process, int Id)
+ {
+ if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
+ {
+ return Dict.GetData(Id);
+ }
+
+ return null;
+ }
+
+ public T GetData<T>(Process Process, int Id)
+ {
+ if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
+ {
+ return Dict.GetData<T>(Id);
+ }
+
+ return default(T);
+ }
+
+ public object Delete(Process Process, int Id)
+ {
+ if (DictByProcess.TryGetValue(Process, out IdDictionary Dict))
+ {
+ return Dict.Delete(Id);
+ }
+
+ return null;
+ }
+
+ public ICollection<object> DeleteProcess(Process Process)
+ {
+ if (DictByProcess.TryRemove(Process, out IdDictionary Dict))
+ {
+ return Dict.Clear();
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Homebrew.cs b/Ryujinx.HLE/HOS/Homebrew.cs
new file mode 100644
index 00000000..1f862a4a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Homebrew.cs
@@ -0,0 +1,77 @@
+using ChocolArm64.Memory;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS
+{
+ static class Homebrew
+ {
+ public const string TemporaryNroSuffix = ".ryu_tmp.nro";
+
+ //http://switchbrew.org/index.php?title=Homebrew_ABI
+ public static void WriteHbAbiData(AMemory Memory, long Position, int MainThreadHandle, string SwitchPath)
+ {
+ //MainThreadHandle.
+ WriteConfigEntry(Memory, ref Position, 1, 0, MainThreadHandle);
+
+ //NextLoadPath.
+ WriteConfigEntry(Memory, ref Position, 2, 0, Position + 0x200, Position + 0x400);
+
+ //Argv.
+ long ArgvPosition = Position + 0xC00;
+
+ Memory.WriteBytes(ArgvPosition, Encoding.ASCII.GetBytes(SwitchPath + "\0"));
+
+ WriteConfigEntry(Memory, ref Position, 5, 0, 0, ArgvPosition);
+
+ //AppletType.
+ WriteConfigEntry(Memory, ref Position, 7);
+
+ //EndOfList.
+ WriteConfigEntry(Memory, ref Position, 0);
+ }
+
+ private static void WriteConfigEntry(
+ AMemory Memory,
+ ref long Position,
+ int Key,
+ int Flags = 0,
+ long Value0 = 0,
+ long Value1 = 0)
+ {
+ Memory.WriteInt32(Position + 0x00, Key);
+ Memory.WriteInt32(Position + 0x04, Flags);
+ Memory.WriteInt64(Position + 0x08, Value0);
+ Memory.WriteInt64(Position + 0x10, Value1);
+
+ Position += 0x18;
+ }
+
+ public static string ReadHbAbiNextLoadPath(AMemory Memory, long Position)
+ {
+ string FileName = null;
+
+ while (true)
+ {
+ long Key = Memory.ReadInt64(Position);
+
+ if (Key == 2)
+ {
+ long Value0 = Memory.ReadInt64(Position + 0x08);
+ long Value1 = Memory.ReadInt64(Position + 0x10);
+
+ FileName = AMemoryHelper.ReadAsciiString(Memory, Value0, Value1 - Value0);
+
+ break;
+ }
+ else if (Key == 0)
+ {
+ break;
+ }
+
+ Position += 0x18;
+ }
+
+ return FileName;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
new file mode 100644
index 00000000..a5579675
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -0,0 +1,227 @@
+using Ryujinx.HLE.HOS.Font;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class Horizon : IDisposable
+ {
+ internal const int HidSize = 0x40000;
+ internal const int FontSize = 0x1100000;
+
+ private Switch Device;
+
+ private KProcessScheduler Scheduler;
+
+ private ConcurrentDictionary<int, Process> Processes;
+
+ public SystemStateMgr State { get; private set; }
+
+ internal KSharedMemory HidSharedMem { get; private set; }
+ internal KSharedMemory FontSharedMem { get; private set; }
+
+ internal SharedFontManager Font { get; private set; }
+
+ internal KEvent VsyncEvent { get; private set; }
+
+ public Horizon(Switch Device)
+ {
+ this.Device = Device;
+
+ Scheduler = new KProcessScheduler(Device.Log);
+
+ Processes = new ConcurrentDictionary<int, Process>();
+
+ State = new SystemStateMgr();
+
+ if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) ||
+ !Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ HidSharedMem = new KSharedMemory(HidPA, HidSize);
+ FontSharedMem = new KSharedMemory(FontPA, FontSize);
+
+ Font = new SharedFontManager(Device, FontSharedMem.PA);
+
+ VsyncEvent = new KEvent();
+ }
+
+ public void LoadCart(string ExeFsDir, string RomFsFile = null)
+ {
+ if (RomFsFile != null)
+ {
+ Device.FileSystem.LoadRomFs(RomFsFile);
+ }
+
+ string NpdmFileName = Path.Combine(ExeFsDir, "main.npdm");
+
+ Npdm MetaData = null;
+
+ if (File.Exists(NpdmFileName))
+ {
+ Device.Log.PrintInfo(LogClass.Loader, $"Loading main.npdm...");
+
+ using (FileStream Input = new FileStream(NpdmFileName, FileMode.Open))
+ {
+ MetaData = new Npdm(Input);
+ }
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
+ }
+
+ Process MainProcess = MakeProcess(MetaData);
+
+ void LoadNso(string FileName)
+ {
+ foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
+ {
+ if (Path.GetExtension(File) != string.Empty)
+ {
+ continue;
+ }
+
+ Device.Log.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}...");
+
+ using (FileStream Input = new FileStream(File, FileMode.Open))
+ {
+ string Name = Path.GetFileNameWithoutExtension(File);
+
+ Nso Program = new Nso(Input, Name);
+
+ MainProcess.LoadProgram(Program);
+ }
+ }
+ }
+
+ if (!MainProcess.MetaData.Is64Bits)
+ {
+ throw new NotImplementedException("32-bit titles are unsupported!");
+ }
+
+ LoadNso("rtld");
+
+ MainProcess.SetEmptyArgs();
+
+ LoadNso("main");
+ LoadNso("subsdk*");
+ LoadNso("sdk");
+
+ MainProcess.Run();
+ }
+
+ public void LoadProgram(string FilePath)
+ {
+ bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
+
+ string Name = Path.GetFileNameWithoutExtension(FilePath);
+ string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
+
+ if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
+ {
+ string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
+ string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
+
+ string SwitchDir = Path.GetDirectoryName(TempPath);
+
+ if (!Directory.Exists(SwitchDir))
+ {
+ Directory.CreateDirectory(SwitchDir);
+ }
+
+ File.Copy(FilePath, TempPath, true);
+
+ FilePath = TempPath;
+ }
+
+ Process MainProcess = MakeProcess();
+
+ using (FileStream Input = new FileStream(FilePath, FileMode.Open))
+ {
+ MainProcess.LoadProgram(IsNro
+ ? (IExecutable)new Nro(Input, FilePath)
+ : (IExecutable)new Nso(Input, FilePath));
+ }
+
+ MainProcess.SetEmptyArgs();
+ MainProcess.Run(IsNro);
+ }
+
+ public void SignalVsync() => VsyncEvent.WaitEvent.Set();
+
+ private Process MakeProcess(Npdm MetaData = null)
+ {
+ Process Process;
+
+ lock (Processes)
+ {
+ int ProcessId = 0;
+
+ while (Processes.ContainsKey(ProcessId))
+ {
+ ProcessId++;
+ }
+
+ Process = new Process(Device, Scheduler, ProcessId, MetaData);
+
+ Processes.TryAdd(ProcessId, Process);
+ }
+
+ InitializeProcess(Process);
+
+ return Process;
+ }
+
+ private void InitializeProcess(Process Process)
+ {
+ Process.AppletState.SetFocus(true);
+ }
+
+ internal void ExitProcess(int ProcessId)
+ {
+ if (Processes.TryRemove(ProcessId, out Process Process))
+ {
+ Process.Dispose();
+
+ if (Processes.Count == 0)
+ {
+ Unload();
+
+ Device.Unload();
+ }
+ }
+ }
+
+ private void Unload()
+ {
+ VsyncEvent.Dispose();
+
+ Scheduler.Dispose();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ foreach (Process Process in Processes.Values)
+ {
+ Process.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/IdDictionary.cs b/Ryujinx.HLE/HOS/IdDictionary.cs
new file mode 100644
index 00000000..03d15498
--- /dev/null
+++ b/Ryujinx.HLE/HOS/IdDictionary.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS
+{
+ class IdDictionary
+ {
+ private ConcurrentDictionary<int, object> Objs;
+
+ 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/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
new file mode 100644
index 00000000..346d696e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcBuffDesc
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+ public int Flags { get; private set; }
+
+ public IpcBuffDesc(BinaryReader Reader)
+ {
+ long Word0 = Reader.ReadUInt32();
+ long Word1 = Reader.ReadUInt32();
+ long Word2 = Reader.ReadUInt32();
+
+ Position = Word1;
+ Position |= (Word2 << 4) & 0x0f00000000;
+ Position |= (Word2 << 34) & 0x7000000000;
+
+ Size = Word0;
+ Size |= (Word2 << 8) & 0xf00000000;
+
+ Flags = (int)Word2 & 3;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
new file mode 100644
index 00000000..081b5695
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
@@ -0,0 +1,92 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ class IpcHandleDesc
+ {
+ public bool HasPId { get; private set; }
+
+ public long 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;
+
+ ToCopy = new int[(Word >> 1) & 0xf];
+ ToMove = new int[(Word >> 5) & 0xf];
+
+ PId = HasPId ? Reader.ReadInt64() : 0;
+
+ for (int Index = 0; Index < ToCopy.Length; Index++)
+ {
+ ToCopy[Index] = Reader.ReadInt32();
+ }
+
+ for (int Index = 0; Index < ToMove.Length; Index++)
+ {
+ ToMove[Index] = Reader.ReadInt32();
+ }
+ }
+
+ 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, long PId) : this(Copy, Move)
+ {
+ this.PId = PId;
+
+ HasPId = true;
+ }
+
+ public static IpcHandleDesc MakeCopy(params int[] Handles)
+ {
+ return new IpcHandleDesc(Handles, new int[0]);
+ }
+
+ public static IpcHandleDesc MakeMove(params int[] Handles)
+ {
+ return new IpcHandleDesc(new int[0], Handles);
+ }
+
+ public byte[] GetBytes()
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ int Word = HasPId ? 1 : 0;
+
+ Word |= (ToCopy.Length & 0xf) << 1;
+ Word |= (ToMove.Length & 0xf) << 5;
+
+ Writer.Write(Word);
+
+ if (HasPId)
+ {
+ Writer.Write((long)PId);
+ }
+
+ foreach (int Handle in ToCopy)
+ {
+ Writer.Write(Handle);
+ }
+
+ foreach (int Handle in ToMove)
+ {
+ Writer.Write(Handle);
+ }
+
+ return MS.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
new file mode 100644
index 00000000..08a4cdb5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
@@ -0,0 +1,140 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Kernel;
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ static class IpcHandler
+ {
+ public static long IpcCall(
+ Switch Ns,
+ Process Process,
+ AMemory Memory,
+ KSession Session,
+ IpcMessage Request,
+ long CmdPtr)
+ {
+ IpcMessage Response = new IpcMessage();
+
+ using (MemoryStream Raw = new MemoryStream(Request.RawData))
+ {
+ BinaryReader ReqReader = new BinaryReader(Raw);
+
+ if (Request.Type == IpcMessageType.Request ||
+ Request.Type == IpcMessageType.RequestWithContext)
+ {
+ Response.Type = IpcMessageType.Response;
+
+ using (MemoryStream ResMS = new MemoryStream())
+ {
+ BinaryWriter ResWriter = new BinaryWriter(ResMS);
+
+ ServiceCtx Context = new ServiceCtx(
+ Ns,
+ Process,
+ Memory,
+ Session,
+ Request,
+ Response,
+ ReqReader,
+ ResWriter);
+
+ Session.Service.CallMethod(Context);
+
+ Response.RawData = ResMS.ToArray();
+ }
+ }
+ else if (Request.Type == IpcMessageType.Control ||
+ Request.Type == IpcMessageType.ControlWithContext)
+ {
+ long Magic = ReqReader.ReadInt64();
+ long CmdId = ReqReader.ReadInt64();
+
+ switch (CmdId)
+ {
+ case 0:
+ {
+ Request = FillResponse(Response, 0, Session.Service.ConvertToDomain());
+
+ break;
+ }
+
+ case 3:
+ {
+ Request = FillResponse(Response, 0, 0x500);
+
+ break;
+ }
+
+ //TODO: Whats the difference between IpcDuplicateSession/Ex?
+ case 2:
+ case 4:
+ {
+ int Unknown = ReqReader.ReadInt32();
+
+ int Handle = Process.HandleTable.OpenHandle(Session);
+
+ Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+
+ Request = FillResponse(Response, 0);
+
+ break;
+ }
+
+ default: throw new NotImplementedException(CmdId.ToString());
+ }
+ }
+ else if (Request.Type == IpcMessageType.CloseSession)
+ {
+ //TODO
+ }
+ else
+ {
+ throw new NotImplementedException(Request.Type.ToString());
+ }
+
+ Memory.WriteBytes(CmdPtr, Response.GetBytes(CmdPtr));
+ }
+
+ return 0;
+ }
+
+ private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ foreach (int Value in Values)
+ {
+ Writer.Write(Value);
+ }
+
+ return FillResponse(Response, Result, MS.ToArray());
+ }
+ }
+
+ private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null)
+ {
+ Response.Type = IpcMessageType.Response;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ Writer.Write(IpcMagic.Sfco);
+ Writer.Write(Result);
+
+ if (Data != null)
+ {
+ Writer.Write(Data);
+ }
+
+ Response.RawData = MS.ToArray();
+ }
+
+ return Response;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs b/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs
new file mode 100644
index 00000000..72770b90
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
new file mode 100644
index 00000000..c8153fdb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
@@ -0,0 +1,215 @@
+using System.Collections.Generic;
+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>();
+ SendBuff = new List<IpcBuffDesc>();
+ ReceiveBuff = new List<IpcBuffDesc>();
+ ExchangeBuff = new List<IpcBuffDesc>();
+ RecvListBuff = new List<IpcRecvListBuffDesc>();
+
+ ObjectIds = new List<int>();
+ }
+
+ public IpcMessage(byte[] Data, long CmdPtr) : this()
+ {
+ using (MemoryStream MS = new MemoryStream(Data))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ Initialize(Reader, CmdPtr);
+ }
+ }
+
+ private void Initialize(BinaryReader Reader, long CmdPtr)
+ {
+ 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);
+ }
+
+ for (int Index = 0; Index < PtrBuffCount; Index++)
+ {
+ PtrBuff.Add(new IpcPtrBuffDesc(Reader));
+ }
+
+ void ReadBuff(List<IpcBuffDesc> Buff, int Count)
+ {
+ for (int Index = 0; Index < Count; Index++)
+ {
+ Buff.Add(new IpcBuffDesc(Reader));
+ }
+ }
+
+ ReadBuff(SendBuff, SendBuffCount);
+ ReadBuff(ReceiveBuff, RecvBuffCount);
+ ReadBuff(ExchangeBuff, XchgBuffCount);
+
+ RawDataSize *= 4;
+
+ long RecvListPos = Reader.BaseStream.Position + RawDataSize;
+
+ long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr);
+
+ 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);
+
+ for (int Index = 0; Index < RecvListCount; Index++)
+ {
+ RecvListBuff.Add(new IpcRecvListBuffDesc(Reader));
+ }
+ }
+
+ public byte[] GetBytes(long CmdPtr)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ 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;
+
+ byte[] HandleData = new byte[0];
+
+ if (HandleDesc != null)
+ {
+ HandleData = HandleDesc.GetBytes();
+ }
+
+ int DataLength = RawData?.Length ?? 0;
+
+ int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length);
+
+ //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;
+
+ if (HandleDesc != null)
+ {
+ Word1 |= 1 << 31;
+ }
+
+ Writer.Write(Word0);
+ Writer.Write(Word1);
+ Writer.Write(HandleData);
+
+ MS.Seek(Pad0, SeekOrigin.Current);
+
+ if (RawData != null)
+ {
+ Writer.Write(RawData);
+ }
+
+ Writer.Write(new byte[Pad1]);
+
+ return MS.ToArray();
+ }
+ }
+
+ private long GetPadSize16(long Position)
+ {
+ if ((Position & 0xf) != 0)
+ {
+ return 0x10 - (Position & 0xf);
+ }
+
+ return 0;
+ }
+
+ public (long Position, long Size) GetBufferType0x21()
+ {
+ if (PtrBuff.Count != 0 &&
+ PtrBuff[0].Position != 0 &&
+ PtrBuff[0].Size != 0)
+ {
+ return (PtrBuff[0].Position, PtrBuff[0].Size);
+ }
+
+ if (SendBuff.Count != 0 &&
+ SendBuff[0].Position != 0 &&
+ SendBuff[0].Size != 0)
+ {
+ return (SendBuff[0].Position, SendBuff[0].Size);
+ }
+
+ return (0, 0);
+ }
+
+ public (long Position, long Size) GetBufferType0x22()
+ {
+ if (RecvListBuff.Count != 0 &&
+ RecvListBuff[0].Position != 0 &&
+ RecvListBuff[0].Size != 0)
+ {
+ return (RecvListBuff[0].Position, RecvListBuff[0].Size);
+ }
+
+ if (ReceiveBuff.Count != 0 &&
+ ReceiveBuff[0].Position != 0 &&
+ ReceiveBuff[0].Size != 0)
+ {
+ return (ReceiveBuff[0].Position, ReceiveBuff[0].Size);
+ }
+
+ return (0, 0);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
new file mode 100644
index 00000000..e258accc
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ enum IpcMessageType
+ {
+ Response = 0,
+ CloseSession = 2,
+ Request = 4,
+ Control = 5,
+ RequestWithContext = 6,
+ ControlWithContext = 7
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
new file mode 100644
index 00000000..21f5d3bc
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs
@@ -0,0 +1,26 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcPtrBuffDesc
+ {
+ public long Position { get; private set; }
+ public int Index { get; private set; }
+ public long Size { get; private set; }
+
+ public IpcPtrBuffDesc(BinaryReader Reader)
+ {
+ long Word0 = Reader.ReadUInt32();
+ long Word1 = Reader.ReadUInt32();
+
+ Position = Word1;
+ Position |= (Word0 << 20) & 0x0f00000000;
+ Position |= (Word0 << 30) & 0x7000000000;
+
+ Index = ((int)Word0 >> 0) & 0x03f;
+ Index |= ((int)Word0 >> 3) & 0x1c0;
+
+ Size = (ushort)(Word0 >> 16);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
new file mode 100644
index 00000000..1d0a8c80
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs
@@ -0,0 +1,19 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Ipc
+{
+ struct IpcRecvListBuffDesc
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public IpcRecvListBuffDesc(BinaryReader Reader)
+ {
+ long Value = Reader.ReadInt64();
+
+ Position = Value & 0xffffffffffff;
+
+ Size = (ushort)(Value >> 48);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs b/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs
new file mode 100644
index 00000000..da4a7e75
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
new file mode 100644
index 00000000..d7df0a72
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/AddressArbiter.cs
@@ -0,0 +1,111 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class AddressArbiter
+ {
+ static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout)
+ {
+ KThread CurrentThread = Process.GetThread(ThreadState.Tpidr);
+
+ Process.Scheduler.SetReschedule(CurrentThread.ProcessorId);
+
+ CurrentThread.ArbiterWaitAddress = Address;
+ CurrentThread.ArbiterSignaled = false;
+
+ Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ if (!CurrentThread.ArbiterSignaled)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return 0;
+ }
+
+ public static ulong WaitForAddressIfLessThan(Process Process,
+ AThreadState ThreadState,
+ AMemory Memory,
+ long Address,
+ int Value,
+ ulong Timeout,
+ bool ShouldDecrement)
+ {
+ Memory.SetExclusive(ThreadState, Address);
+
+ int CurrentValue = Memory.ReadInt32(Address);
+
+ while (true)
+ {
+ if (Memory.TestExclusive(ThreadState, Address))
+ {
+ if (CurrentValue < Value)
+ {
+ if (ShouldDecrement)
+ {
+ Memory.WriteInt32(Address, CurrentValue - 1);
+ }
+
+ Memory.ClearExclusiveForStore(ThreadState);
+ }
+ else
+ {
+ Memory.ClearExclusiveForStore(ThreadState);
+
+ return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+ }
+
+ break;
+ }
+
+ Memory.SetExclusive(ThreadState, Address);
+
+ CurrentValue = Memory.ReadInt32(Address);
+ }
+
+ if (Timeout == 0)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return WaitForAddress(Process, ThreadState, Address, Timeout);
+ }
+
+ public static ulong WaitForAddressIfEqual(Process Process,
+ AThreadState ThreadState,
+ AMemory Memory,
+ long Address,
+ int Value,
+ ulong Timeout)
+ {
+ if (Memory.ReadInt32(Address) != Value)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+ }
+
+ if (Timeout == 0)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+
+ return WaitForAddress(Process, ThreadState, Address, Timeout);
+ }
+ }
+
+ enum ArbitrationType : int
+ {
+ WaitIfLessThan,
+ DecrementAndWaitIfLessThan,
+ WaitIfEqual
+ }
+
+ enum SignalType : int
+ {
+ Signal,
+ IncrementAndSignalIfEqual,
+ ModifyByWaitingCountAndSignalIfEqual
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
new file mode 100644
index 00000000..c97caf42
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ enum AddressSpaceType
+ {
+ Addr32Bits = 0,
+ Addr36Bits = 1,
+ Addr36BitsNoMap = 2,
+ Addr39Bits = 3
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/KEvent.cs
new file mode 100644
index 00000000..eaaafaba
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KEvent.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KEvent : KSynchronizationObject { }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
new file mode 100644
index 00000000..6100741b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryBlock
+ {
+ public long BasePosition { get; set; }
+ public long PagesCount { get; set; }
+
+ public MemoryState State { get; set; }
+ public MemoryPermission Permission { get; set; }
+ public MemoryAttribute Attribute { get; set; }
+
+ public int IpcRefCount { get; set; }
+ public int DeviceRefCount { get; set; }
+
+ public KMemoryBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState State,
+ MemoryPermission Permission,
+ MemoryAttribute Attribute)
+ {
+ this.BasePosition = BasePosition;
+ this.PagesCount = PagesCount;
+ this.State = State;
+ this.Attribute = Attribute;
+ this.Permission = Permission;
+ }
+
+ public KMemoryInfo GetInfo()
+ {
+ long Size = PagesCount * KMemoryManager.PageSize;
+
+ return new KMemoryInfo(
+ BasePosition,
+ Size,
+ State,
+ Permission,
+ Attribute,
+ IpcRefCount,
+ DeviceRefCount);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
new file mode 100644
index 00000000..9b73b32b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryInfo
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public MemoryState State { get; private set; }
+ public MemoryPermission Permission { get; private set; }
+ public MemoryAttribute Attribute { get; private set; }
+
+ public int IpcRefCount { get; private set; }
+ public int DeviceRefCount { get; private set; }
+
+ public KMemoryInfo(
+ long Position,
+ long Size,
+ MemoryState State,
+ MemoryPermission Permission,
+ MemoryAttribute Attribute,
+ int IpcRefCount,
+ int DeviceRefCount)
+ {
+ this.Position = Position;
+ this.Size = Size;
+ this.State = State;
+ this.Attribute = Attribute;
+ this.Permission = Permission;
+ this.IpcRefCount = IpcRefCount;
+ this.DeviceRefCount = DeviceRefCount;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
new file mode 100644
index 00000000..0432aa88
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs
@@ -0,0 +1,1081 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Memory;
+using System;
+using System.Collections.Generic;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KMemoryManager
+ {
+ public const int PageSize = 0x1000;
+
+ private LinkedList<KMemoryBlock> Blocks;
+
+ private AMemory CpuMemory;
+
+ private ArenaAllocator Allocator;
+
+ public long AddrSpaceStart { get; private set; }
+ public long AddrSpaceEnd { get; private set; }
+
+ public long CodeRegionStart { get; private set; }
+ public long CodeRegionEnd { get; private set; }
+
+ public long MapRegionStart { get; private set; }
+ public long MapRegionEnd { get; private set; }
+
+ public long HeapRegionStart { get; private set; }
+ public long HeapRegionEnd { get; private set; }
+
+ public long NewMapRegionStart { get; private set; }
+ public long NewMapRegionEnd { get; private set; }
+
+ public long TlsIoRegionStart { get; private set; }
+ public long TlsIoRegionEnd { get; private set; }
+
+ public long PersonalMmHeapUsage { get; private set; }
+
+ private long CurrentHeapAddr;
+
+ public KMemoryManager(Process Process)
+ {
+ CpuMemory = Process.Memory;
+ Allocator = Process.Device.Memory.Allocator;
+
+ long CodeRegionSize;
+ long MapRegionSize;
+ long HeapRegionSize;
+ long NewMapRegionSize;
+ long TlsIoRegionSize;
+ int AddrSpaceWidth;
+
+ AddressSpaceType AddrType = AddressSpaceType.Addr39Bits;
+
+ if (Process.MetaData != null)
+ {
+ AddrType = (AddressSpaceType)Process.MetaData.AddressSpaceWidth;
+ }
+
+ switch (AddrType)
+ {
+ case AddressSpaceType.Addr32Bits:
+ CodeRegionStart = 0x200000;
+ CodeRegionSize = 0x3fe00000;
+ MapRegionSize = 0x40000000;
+ HeapRegionSize = 0x40000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 32;
+ break;
+
+ case AddressSpaceType.Addr36Bits:
+ CodeRegionStart = 0x8000000;
+ CodeRegionSize = 0x78000000;
+ MapRegionSize = 0x180000000;
+ HeapRegionSize = 0x180000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 36;
+ break;
+
+ case AddressSpaceType.Addr36BitsNoMap:
+ CodeRegionStart = 0x200000;
+ CodeRegionSize = 0x3fe00000;
+ MapRegionSize = 0;
+ HeapRegionSize = 0x80000000;
+ NewMapRegionSize = 0;
+ TlsIoRegionSize = 0;
+ AddrSpaceWidth = 36;
+ break;
+
+ case AddressSpaceType.Addr39Bits:
+ CodeRegionStart = 0;
+ CodeRegionSize = 0x80000000;
+ MapRegionSize = 0x1000000000;
+ HeapRegionSize = 0x180000000;
+ NewMapRegionSize = 0x80000000;
+ TlsIoRegionSize = 0x1000000000;
+ AddrSpaceWidth = 39;
+ break;
+
+ default: throw new InvalidOperationException();
+ }
+
+ AddrSpaceStart = 0;
+ AddrSpaceEnd = 1L << AddrSpaceWidth;
+
+ CodeRegionEnd = CodeRegionStart + CodeRegionSize;
+ MapRegionStart = CodeRegionEnd;
+ MapRegionEnd = CodeRegionEnd + MapRegionSize;
+ HeapRegionStart = MapRegionEnd;
+ HeapRegionEnd = MapRegionEnd + HeapRegionSize;
+ NewMapRegionStart = HeapRegionEnd;
+ NewMapRegionEnd = HeapRegionEnd + NewMapRegionSize;
+ TlsIoRegionStart = NewMapRegionEnd;
+ TlsIoRegionEnd = NewMapRegionEnd + TlsIoRegionSize;
+
+ CurrentHeapAddr = HeapRegionStart;
+
+ if (NewMapRegionSize == 0)
+ {
+ NewMapRegionStart = AddrSpaceStart;
+ NewMapRegionEnd = AddrSpaceEnd;
+ }
+
+ Blocks = new LinkedList<KMemoryBlock>();
+
+ long AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize;
+
+ InsertBlock(AddrSpaceStart, AddrSpacePagesCount, MemoryState.Unmapped);
+ }
+
+ public void HleMapProcessCode(long Position, long Size)
+ {
+ long PagesCount = Size / PageSize;
+
+ if (!Allocator.TryAllocate(Size, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ lock (Blocks)
+ {
+ InsertBlock(Position, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute);
+
+ CpuMemory.Map(Position, PA, Size);
+ }
+ }
+
+ public void HleMapCustom(long Position, long Size, MemoryState State, MemoryPermission Permission)
+ {
+ long PagesCount = Size / PageSize;
+
+ if (!Allocator.TryAllocate(Size, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ lock (Blocks)
+ {
+ InsertBlock(Position, PagesCount, State, Permission);
+
+ CpuMemory.Map(Position, PA, Size);
+ }
+ }
+
+ public long HleMapTlsPage()
+ {
+ bool HasTlsIoRegion = TlsIoRegionStart != TlsIoRegionEnd;
+
+ long Position = HasTlsIoRegion ? TlsIoRegionStart : CodeRegionStart;
+
+ lock (Blocks)
+ {
+ while (Position < (HasTlsIoRegion ? TlsIoRegionEnd : CodeRegionEnd))
+ {
+ if (FindBlock(Position).State == MemoryState.Unmapped)
+ {
+ InsertBlock(Position, 1, MemoryState.ThreadLocal, MemoryPermission.ReadAndWrite);
+
+ if (!Allocator.TryAllocate(PageSize, out long PA))
+ {
+ throw new InvalidOperationException();
+ }
+
+ CpuMemory.Map(Position, PA, PageSize);
+
+ return Position;
+ }
+
+ Position += PageSize;
+ }
+
+ throw new InvalidOperationException();
+ }
+ }
+
+ public long TrySetHeapSize(long Size, out long Position)
+ {
+ Position = 0;
+
+ if ((ulong)Size > (ulong)(HeapRegionEnd - HeapRegionStart))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ bool Success = false;
+
+ long CurrentHeapSize = GetHeapSize();
+
+ if ((ulong)CurrentHeapSize <= (ulong)Size)
+ {
+ //Expand.
+ long DiffSize = Size - CurrentHeapSize;
+
+ lock (Blocks)
+ {
+ if (Success = IsUnmapped(CurrentHeapAddr, DiffSize))
+ {
+ if (!Allocator.TryAllocate(DiffSize, out long PA))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ long PagesCount = DiffSize / PageSize;
+
+ InsertBlock(CurrentHeapAddr, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite);
+
+ CpuMemory.Map(CurrentHeapAddr, PA, DiffSize);
+ }
+ }
+ }
+ else
+ {
+ //Shrink.
+ long FreeAddr = HeapRegionStart + Size;
+ long DiffSize = CurrentHeapSize - Size;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ FreeAddr,
+ DiffSize,
+ MemoryState.Mask,
+ MemoryState.Heap,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ if (Success)
+ {
+ long PagesCount = DiffSize / PageSize;
+
+ InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(FreeAddr, DiffSize);
+
+ FreePages(FreeAddr, PagesCount);
+ }
+ }
+ }
+
+ CurrentHeapAddr = HeapRegionStart + Size;
+
+ if (Success)
+ {
+ Position = HeapRegionStart;
+
+ return 0;
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long GetHeapSize()
+ {
+ return CurrentHeapAddr - HeapRegionStart;
+ }
+
+ public long SetMemoryAttribute(
+ long Position,
+ long Size,
+ MemoryAttribute AttributeMask,
+ MemoryAttribute AttributeValue)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.AttributeChangeAllowed,
+ MemoryState.AttributeChangeAllowed,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.BorrowedAndIpcMapped,
+ MemoryAttribute.None,
+ MemoryAttribute.DeviceMappedAndUncached,
+ out MemoryState State,
+ out MemoryPermission Permission,
+ out MemoryAttribute Attribute))
+ {
+ long PagesCount = Size / PageSize;
+
+ Attribute &= ~AttributeMask;
+ Attribute |= AttributeMask & AttributeValue;
+
+ InsertBlock(Position, PagesCount, State, Permission, Attribute);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public KMemoryInfo QueryMemory(long Position)
+ {
+ if ((ulong)Position >= (ulong)AddrSpaceStart &&
+ (ulong)Position < (ulong)AddrSpaceEnd)
+ {
+ lock (Blocks)
+ {
+ return FindBlock(Position).GetInfo();
+ }
+ }
+ else
+ {
+ return new KMemoryInfo(
+ AddrSpaceEnd,
+ -AddrSpaceEnd,
+ MemoryState.Reserved,
+ MemoryPermission.None,
+ MemoryAttribute.None,
+ 0,
+ 0);
+ }
+ }
+
+ public long Map(long Src, long Dst, long Size)
+ {
+ bool Success;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ Src,
+ Size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState SrcState,
+ out _,
+ out _);
+
+ Success &= IsUnmapped(Dst, Size);
+
+ if (Success)
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Src, PagesCount, SrcState, MemoryPermission.None, MemoryAttribute.Borrowed);
+
+ InsertBlock(Dst, PagesCount, MemoryState.MappedMemory, MemoryPermission.ReadAndWrite);
+
+ long PA = CpuMemory.GetPhysicalAddress(Src);
+
+ CpuMemory.Map(Dst, PA, Size);
+ }
+ }
+
+ return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long Unmap(long Src, long Dst, long Size)
+ {
+ bool Success;
+
+ lock (Blocks)
+ {
+ Success = CheckRange(
+ Src,
+ Size,
+ MemoryState.MapAllowed,
+ MemoryState.MapAllowed,
+ MemoryPermission.Mask,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState SrcState,
+ out _,
+ out _);
+
+ Success &= CheckRange(
+ Dst,
+ Size,
+ MemoryState.Mask,
+ MemoryState.MappedMemory,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+
+ if (Success)
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Src, PagesCount, SrcState, MemoryPermission.ReadAndWrite);
+
+ InsertBlock(Dst, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Dst, Size);
+ }
+ }
+
+ return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long MapSharedMemory(KSharedMemory SharedMemory, MemoryPermission Permission, long Position)
+ {
+ lock (Blocks)
+ {
+ if (IsUnmapped(Position, SharedMemory.Size))
+ {
+ long PagesCount = SharedMemory.Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.SharedMemory, Permission);
+
+ CpuMemory.Map(Position, SharedMemory.PA, SharedMemory.Size);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long UnmapSharedMemory(long Position, long Size)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.Mask,
+ MemoryState.SharedMemory,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Position, Size);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long ReserveTransferMemory(long Position, long Size, MemoryPermission Permission)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryPermission.Mask,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out MemoryAttribute Attribute))
+ {
+ long PagesCount = Size / PageSize;
+
+ Attribute |= MemoryAttribute.Borrowed;
+
+ InsertBlock(Position, PagesCount, State, Permission, Attribute);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long ResetTransferMemory(long Position, long Size)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.Borrowed,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, State, MemoryPermission.ReadAndWrite);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long SetProcessMemoryPermission(long Position, long Size, MemoryPermission Permission)
+ {
+ lock (Blocks)
+ {
+ if (CheckRange(
+ Position,
+ Size,
+ MemoryState.ProcessPermissionChangeAllowed,
+ MemoryState.ProcessPermissionChangeAllowed,
+ MemoryPermission.None,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out MemoryState State,
+ out _,
+ out _))
+ {
+ if (State == MemoryState.CodeStatic)
+ {
+ State = MemoryState.CodeMutable;
+ }
+ else if (State == MemoryState.ModCodeStatic)
+ {
+ State = MemoryState.ModCodeMutable;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, State, Permission);
+
+ return 0;
+ }
+ }
+
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ public long MapPhysicalMemory(long Position, long Size)
+ {
+ long End = Position + Size;
+
+ lock (Blocks)
+ {
+ long MappedSize = 0;
+
+ KMemoryInfo Info;
+
+ LinkedListNode<KMemoryBlock> BaseNode = FindBlockNode(Position);
+
+ LinkedListNode<KMemoryBlock> Node = BaseNode;
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State != MemoryState.Unmapped)
+ {
+ MappedSize += GetSizeInRange(Info, Position, End);
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ if (MappedSize == Size)
+ {
+ return 0;
+ }
+
+ long RemainingSize = Size - MappedSize;
+
+ if (!Allocator.TryAllocate(RemainingSize, out long PA))
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory);
+ }
+
+ Node = BaseNode;
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State == MemoryState.Unmapped)
+ {
+ long CurrSize = GetSizeInRange(Info, Position, End);
+
+ CpuMemory.Map(Info.Position, PA, CurrSize);
+
+ PA += CurrSize;
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ PersonalMmHeapUsage += RemainingSize;
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(
+ Position,
+ PagesCount,
+ MemoryState.Unmapped,
+ MemoryPermission.None,
+ MemoryAttribute.None,
+ MemoryState.Heap,
+ MemoryPermission.ReadAndWrite,
+ MemoryAttribute.None);
+ }
+
+ return 0;
+ }
+
+ public long UnmapPhysicalMemory(long Position, long Size)
+ {
+ long End = Position + Size;
+
+ lock (Blocks)
+ {
+ long HeapMappedSize = 0;
+
+ long CurrPosition = Position;
+
+ KMemoryInfo Info;
+
+ LinkedListNode<KMemoryBlock> Node = FindBlockNode(CurrPosition);
+
+ do
+ {
+ Info = Node.Value.GetInfo();
+
+ if (Info.State == MemoryState.Heap)
+ {
+ if (Info.Attribute != MemoryAttribute.None)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ HeapMappedSize += GetSizeInRange(Info, Position, End);
+ }
+ else if (Info.State != MemoryState.Unmapped)
+ {
+ return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+ }
+
+ Node = Node.Next;
+ }
+ while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null);
+
+ if (HeapMappedSize == 0)
+ {
+ return 0;
+ }
+
+ PersonalMmHeapUsage -= HeapMappedSize;
+
+ long PagesCount = Size / PageSize;
+
+ InsertBlock(Position, PagesCount, MemoryState.Unmapped);
+
+ CpuMemory.Unmap(Position, Size);
+
+ FreePages(Position, PagesCount);
+
+ return 0;
+ }
+ }
+
+ private long GetSizeInRange(KMemoryInfo Info, long Start, long End)
+ {
+ long CurrEnd = Info.Size + Info.Position;
+ long CurrSize = Info.Size;
+
+ if ((ulong)Info.Position < (ulong)Start)
+ {
+ CurrSize -= Start - Info.Position;
+ }
+
+ if ((ulong)CurrEnd > (ulong)End)
+ {
+ CurrSize -= CurrEnd - End;
+ }
+
+ return CurrSize;
+ }
+
+ private void FreePages(long Position, long PagesCount)
+ {
+ for (long Page = 0; Page < PagesCount; Page++)
+ {
+ long VA = Position + Page * PageSize;
+
+ long PA = CpuMemory.GetPhysicalAddress(VA);
+
+ Allocator.Free(PA, PageSize);
+ }
+ }
+
+ private bool IsUnmapped(long Position, long Size)
+ {
+ return CheckRange(
+ Position,
+ Size,
+ MemoryState.Mask,
+ MemoryState.Unmapped,
+ MemoryPermission.Mask,
+ MemoryPermission.None,
+ MemoryAttribute.Mask,
+ MemoryAttribute.None,
+ MemoryAttribute.IpcAndDeviceMapped,
+ out _,
+ out _,
+ out _);
+ }
+
+ private bool CheckRange(
+ long Position,
+ long Size,
+ MemoryState StateMask,
+ MemoryState StateExpected,
+ MemoryPermission PermissionMask,
+ MemoryPermission PermissionExpected,
+ MemoryAttribute AttributeMask,
+ MemoryAttribute AttributeExpected,
+ MemoryAttribute AttributeIgnoreMask,
+ out MemoryState OutState,
+ out MemoryPermission OutPermission,
+ out MemoryAttribute OutAttribute)
+ {
+ KMemoryInfo BlkInfo = FindBlock(Position).GetInfo();
+
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ if (End <= (ulong)(BlkInfo.Position + BlkInfo.Size))
+ {
+ if ((BlkInfo.Attribute & AttributeMask) == AttributeExpected &&
+ (BlkInfo.State & StateMask) == StateExpected &&
+ (BlkInfo.Permission & PermissionMask) == PermissionExpected)
+ {
+ OutState = BlkInfo.State;
+ OutPermission = BlkInfo.Permission;
+ OutAttribute = BlkInfo.Attribute & ~AttributeIgnoreMask;
+
+ return true;
+ }
+ }
+
+ OutState = MemoryState.Unmapped;
+ OutPermission = MemoryPermission.None;
+ OutAttribute = MemoryAttribute.None;
+
+ return false;
+ }
+
+ private void InsertBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState OldState,
+ MemoryPermission OldPermission,
+ MemoryAttribute OldAttribute,
+ MemoryState NewState,
+ MemoryPermission 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.
+ OldAttribute |= MemoryAttribute.IpcAndDeviceMapped;
+
+ ulong Start = (ulong)BasePosition;
+ ulong End = (ulong)PagesCount * PageSize + Start;
+
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ LinkedListNode<KMemoryBlock> NewNode = Node;
+ LinkedListNode<KMemoryBlock> NextNode = Node.Next;
+
+ KMemoryBlock CurrBlock = Node.Value;
+
+ ulong CurrStart = (ulong)CurrBlock.BasePosition;
+ ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart;
+
+ if (Start < CurrEnd && CurrStart < End)
+ {
+ MemoryAttribute CurrBlockAttr = CurrBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped;
+
+ if (CurrBlock.State != OldState ||
+ CurrBlock.Permission != OldPermission ||
+ CurrBlockAttr != OldAttribute)
+ {
+ Node = NextNode;
+
+ continue;
+ }
+
+ if (CurrStart >= Start && CurrEnd <= End)
+ {
+ CurrBlock.State = NewState;
+ CurrBlock.Permission = NewPermission;
+ CurrBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped;
+ CurrBlock.Attribute |= NewAttribute;
+ }
+ else if (CurrStart >= Start)
+ {
+ CurrBlock.BasePosition = (long)End;
+
+ CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize);
+
+ long NewPagesCount = (long)((End - CurrStart) / PageSize);
+
+ NewNode = Blocks.AddBefore(Node, new KMemoryBlock(
+ (long)CurrStart,
+ NewPagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+ }
+ else if (CurrEnd <= End)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NewPagesCount = (long)((CurrEnd - Start) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, new KMemoryBlock(
+ BasePosition,
+ NewPagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+ }
+ else
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NextPagesCount = (long)((CurrEnd - End) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, new KMemoryBlock(
+ BasePosition,
+ PagesCount,
+ NewState,
+ NewPermission,
+ NewAttribute));
+
+ Blocks.AddAfter(NewNode, new KMemoryBlock(
+ (long)End,
+ NextPagesCount,
+ CurrBlock.State,
+ CurrBlock.Permission,
+ CurrBlock.Attribute));
+
+ NextNode = null;
+ }
+
+ MergeEqualStateNeighbours(NewNode);
+ }
+
+ Node = NextNode;
+ }
+ }
+
+ private void InsertBlock(
+ long BasePosition,
+ long PagesCount,
+ MemoryState State,
+ MemoryPermission Permission = MemoryPermission.None,
+ MemoryAttribute Attribute = MemoryAttribute.None)
+ {
+ //Inserts new block at the list, replacing and spliting
+ //existing blocks as needed.
+ KMemoryBlock Block = new KMemoryBlock(BasePosition, PagesCount, State, Permission, Attribute);
+
+ ulong Start = (ulong)BasePosition;
+ ulong End = (ulong)PagesCount * PageSize + Start;
+
+ LinkedListNode<KMemoryBlock> NewNode = null;
+
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ KMemoryBlock CurrBlock = Node.Value;
+
+ LinkedListNode<KMemoryBlock> NextNode = Node.Next;
+
+ ulong CurrStart = (ulong)CurrBlock.BasePosition;
+ ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart;
+
+ if (Start < CurrEnd && CurrStart < End)
+ {
+ if (Start >= CurrStart && End <= CurrEnd)
+ {
+ Block.Attribute |= CurrBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped;
+ }
+
+ if (Start > CurrStart && End < CurrEnd)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ long NextPagesCount = (long)((CurrEnd - End) / PageSize);
+
+ NewNode = Blocks.AddAfter(Node, Block);
+
+ Blocks.AddAfter(NewNode, new KMemoryBlock(
+ (long)End,
+ NextPagesCount,
+ CurrBlock.State,
+ CurrBlock.Permission,
+ CurrBlock.Attribute));
+
+ break;
+ }
+ else if (Start <= CurrStart && End < CurrEnd)
+ {
+ CurrBlock.BasePosition = (long)End;
+
+ CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize);
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddBefore(Node, Block);
+ }
+ }
+ else if (Start > CurrStart && End >= CurrEnd)
+ {
+ CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize);
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddAfter(Node, Block);
+ }
+ }
+ else
+ {
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddBefore(Node, Block);
+ }
+
+ Blocks.Remove(Node);
+ }
+ }
+
+ Node = NextNode;
+ }
+
+ if (NewNode == null)
+ {
+ NewNode = Blocks.AddFirst(Block);
+ }
+
+ MergeEqualStateNeighbours(NewNode);
+ }
+
+ private void MergeEqualStateNeighbours(LinkedListNode<KMemoryBlock> Node)
+ {
+ KMemoryBlock Block = Node.Value;
+
+ ulong Start = (ulong)Block.BasePosition;
+ ulong End = (ulong)Block.PagesCount * PageSize + Start;
+
+ if (Node.Previous != null)
+ {
+ KMemoryBlock Previous = Node.Previous.Value;
+
+ if (BlockStateEquals(Block, Previous))
+ {
+ Blocks.Remove(Node.Previous);
+
+ Block.BasePosition = Previous.BasePosition;
+
+ Start = (ulong)Block.BasePosition;
+ }
+ }
+
+ if (Node.Next != null)
+ {
+ KMemoryBlock Next = Node.Next.Value;
+
+ if (BlockStateEquals(Block, Next))
+ {
+ Blocks.Remove(Node.Next);
+
+ End = (ulong)(Next.BasePosition + Next.PagesCount * PageSize);
+ }
+ }
+
+ Block.PagesCount = (long)((End - Start) / PageSize);
+ }
+
+ private static bool BlockStateEquals(KMemoryBlock LHS, KMemoryBlock RHS)
+ {
+ return LHS.State == RHS.State &&
+ LHS.Permission == RHS.Permission &&
+ LHS.Attribute == RHS.Attribute &&
+ LHS.DeviceRefCount == RHS.DeviceRefCount &&
+ LHS.IpcRefCount == RHS.IpcRefCount;
+ }
+
+ private KMemoryBlock FindBlock(long Position)
+ {
+ return FindBlockNode(Position)?.Value;
+ }
+
+ private LinkedListNode<KMemoryBlock> FindBlockNode(long Position)
+ {
+ ulong Addr = (ulong)Position;
+
+ lock (Blocks)
+ {
+ LinkedListNode<KMemoryBlock> Node = Blocks.First;
+
+ while (Node != null)
+ {
+ KMemoryBlock Block = Node.Value;
+
+ ulong Start = (ulong)Block.BasePosition;
+ ulong End = (ulong)Block.PagesCount * PageSize + Start;
+
+ if (Start <= Addr && End - 1 >= Addr)
+ {
+ return Node;
+ }
+
+ Node = Node.Next;
+ }
+ }
+
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
new file mode 100644
index 00000000..db0eaa44
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KProcessHandleTable
+ {
+ private IdDictionary Handles;
+
+ public KProcessHandleTable()
+ {
+ Handles = new IdDictionary();
+ }
+
+ public int OpenHandle(object Obj)
+ {
+ return Handles.Add(Obj);
+ }
+
+ public T GetData<T>(int Handle)
+ {
+ return Handles.GetData<T>(Handle);
+ }
+
+ public object CloseHandle(int Handle)
+ {
+ return Handles.Delete(Handle);
+ }
+
+ public ICollection<object> Clear()
+ {
+ return Handles.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
new file mode 100644
index 00000000..2120f16c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KProcessScheduler.cs
@@ -0,0 +1,370 @@
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KProcessScheduler : IDisposable
+ {
+ private ConcurrentDictionary<KThread, SchedulerThread> AllThreads;
+
+ private ThreadQueue WaitingToRun;
+
+ private KThread[] CoreThreads;
+
+ private bool[] CoreReschedule;
+
+ private object SchedLock;
+
+ private Logger Log;
+
+ public KProcessScheduler(Logger Log)
+ {
+ this.Log = Log;
+
+ AllThreads = new ConcurrentDictionary<KThread, SchedulerThread>();
+
+ WaitingToRun = new ThreadQueue();
+
+ CoreThreads = new KThread[4];
+
+ CoreReschedule = new bool[4];
+
+ SchedLock = new object();
+ }
+
+ public void StartThread(KThread Thread)
+ {
+ lock (SchedLock)
+ {
+ SchedulerThread SchedThread = new SchedulerThread(Thread);
+
+ if (!AllThreads.TryAdd(Thread, SchedThread))
+ {
+ return;
+ }
+
+ if (TryAddToCore(Thread))
+ {
+ Thread.Thread.Execute();
+
+ PrintDbgThreadInfo(Thread, "running.");
+ }
+ else
+ {
+ WaitingToRun.Push(SchedThread);
+
+ PrintDbgThreadInfo(Thread, "waiting to run.");
+ }
+ }
+ }
+
+ public void RemoveThread(KThread Thread)
+ {
+ PrintDbgThreadInfo(Thread, "exited.");
+
+ lock (SchedLock)
+ {
+ if (AllThreads.TryRemove(Thread, out SchedulerThread SchedThread))
+ {
+ WaitingToRun.Remove(SchedThread);
+
+ SchedThread.Dispose();
+ }
+
+ int ActualCore = Thread.ActualCore;
+
+ SchedulerThread NewThread = WaitingToRun.Pop(ActualCore);
+
+ if (NewThread == null)
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {ActualCore}!");
+
+ CoreThreads[ActualCore] = null;
+
+ return;
+ }
+
+ NewThread.Thread.ActualCore = ActualCore;
+
+ RunThread(NewThread);
+ }
+ }
+
+ public void SetThreadActivity(KThread Thread, bool Active)
+ {
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ SchedThread.IsActive = Active;
+
+ if (Active)
+ {
+ SchedThread.WaitActivity.Set();
+ }
+ else
+ {
+ SchedThread.WaitActivity.Reset();
+ }
+ }
+
+ public void EnterWait(KThread Thread, int TimeoutMs = Timeout.Infinite)
+ {
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ Suspend(Thread);
+
+ SchedThread.WaitSync.WaitOne(TimeoutMs);
+
+ TryResumingExecution(SchedThread);
+ }
+
+ public void WakeUp(KThread Thread)
+ {
+ AllThreads[Thread].WaitSync.Set();
+ }
+
+ public void ForceWakeUp(KThread Thread)
+ {
+ if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+ {
+ SchedThread.WaitSync.Set();
+ SchedThread.WaitActivity.Set();
+ SchedThread.WaitSched.Set();
+ }
+ }
+
+ public void ChangeCore(KThread Thread, int IdealCore, int CoreMask)
+ {
+ lock (SchedLock)
+ {
+ if (IdealCore != -3)
+ {
+ Thread.IdealCore = IdealCore;
+ }
+
+ Thread.CoreMask = CoreMask;
+
+ if (AllThreads.ContainsKey(Thread))
+ {
+ SetReschedule(Thread.ActualCore);
+
+ SchedulerThread SchedThread = AllThreads[Thread];
+
+ //Note: Aways if the thread is on the queue first, and try
+ //adding to a new core later, to ensure that a thread that
+ //is already running won't be added to another core.
+ if (WaitingToRun.HasThread(SchedThread) && TryAddToCore(Thread))
+ {
+ WaitingToRun.Remove(SchedThread);
+
+ RunThread(SchedThread);
+ }
+ }
+ }
+ }
+
+ public void Suspend(KThread Thread)
+ {
+ lock (SchedLock)
+ {
+ PrintDbgThreadInfo(Thread, "suspended.");
+
+ int ActualCore = Thread.ActualCore;
+
+ CoreReschedule[ActualCore] = false;
+
+ SchedulerThread SchedThread = WaitingToRun.Pop(ActualCore);
+
+ if (SchedThread != null)
+ {
+ SchedThread.Thread.ActualCore = ActualCore;
+
+ CoreThreads[ActualCore] = SchedThread.Thread;
+
+ RunThread(SchedThread);
+ }
+ else
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, $"Nothing to run on core {Thread.ActualCore}!");
+
+ CoreThreads[ActualCore] = null;
+ }
+ }
+ }
+
+ public void SetReschedule(int Core)
+ {
+ lock (SchedLock)
+ {
+ CoreReschedule[Core] = true;
+ }
+ }
+
+ public void Reschedule(KThread Thread)
+ {
+ bool NeedsReschedule;
+
+ lock (SchedLock)
+ {
+ int ActualCore = Thread.ActualCore;
+
+ NeedsReschedule = CoreReschedule[ActualCore];
+
+ CoreReschedule[ActualCore] = false;
+ }
+
+ if (NeedsReschedule)
+ {
+ Yield(Thread, Thread.ActualPriority - 1);
+ }
+ }
+
+ public void Yield(KThread Thread)
+ {
+ Yield(Thread, Thread.ActualPriority);
+ }
+
+ private void Yield(KThread Thread, int MinPriority)
+ {
+ PrintDbgThreadInfo(Thread, "yielded execution.");
+
+ lock (SchedLock)
+ {
+ int ActualCore = Thread.ActualCore;
+
+ SchedulerThread NewThread = WaitingToRun.Pop(ActualCore, MinPriority);
+
+ if (NewThread != null)
+ {
+ NewThread.Thread.ActualCore = ActualCore;
+
+ CoreThreads[ActualCore] = NewThread.Thread;
+
+ RunThread(NewThread);
+ }
+ else
+ {
+ CoreThreads[ActualCore] = null;
+ }
+ }
+
+ Resume(Thread);
+ }
+
+ public void Resume(KThread Thread)
+ {
+ TryResumingExecution(AllThreads[Thread]);
+ }
+
+ private void TryResumingExecution(SchedulerThread SchedThread)
+ {
+ KThread Thread = SchedThread.Thread;
+
+ PrintDbgThreadInfo(Thread, "trying to resume...");
+
+ SchedThread.WaitActivity.WaitOne();
+
+ lock (SchedLock)
+ {
+ if (TryAddToCore(Thread))
+ {
+ PrintDbgThreadInfo(Thread, "resuming execution...");
+
+ return;
+ }
+
+ WaitingToRun.Push(SchedThread);
+
+ SetReschedule(Thread.ProcessorId);
+
+ PrintDbgThreadInfo(Thread, "entering wait state...");
+ }
+
+ SchedThread.WaitSched.WaitOne();
+
+ PrintDbgThreadInfo(Thread, "resuming execution...");
+ }
+
+ private void RunThread(SchedulerThread SchedThread)
+ {
+ if (!SchedThread.Thread.Thread.Execute())
+ {
+ PrintDbgThreadInfo(SchedThread.Thread, "waked.");
+
+ SchedThread.WaitSched.Set();
+ }
+ else
+ {
+ PrintDbgThreadInfo(SchedThread.Thread, "running.");
+ }
+ }
+
+ public void Resort(KThread Thread)
+ {
+ if (AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+ {
+ WaitingToRun.Resort(SchedThread);
+ }
+ }
+
+ private bool TryAddToCore(KThread Thread)
+ {
+ //First, try running it on Ideal Core.
+ int IdealCore = Thread.IdealCore;
+
+ if (IdealCore != -1 && CoreThreads[IdealCore] == null)
+ {
+ Thread.ActualCore = IdealCore;
+
+ CoreThreads[IdealCore] = Thread;
+
+ return true;
+ }
+
+ //If that fails, then try running on any core allowed by Core Mask.
+ int CoreMask = Thread.CoreMask;
+
+ for (int Core = 0; Core < CoreThreads.Length; Core++, CoreMask >>= 1)
+ {
+ if ((CoreMask & 1) != 0 && CoreThreads[Core] == null)
+ {
+ Thread.ActualCore = Core;
+
+ CoreThreads[Core] = Thread;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void PrintDbgThreadInfo(KThread Thread, string Message)
+ {
+ Log.PrintDebug(LogClass.KernelScheduler, "(" +
+ "ThreadId = " + Thread.ThreadId + ", " +
+ "CoreMask = 0x" + Thread.CoreMask.ToString("x1") + ", " +
+ "ActualCore = " + Thread.ActualCore + ", " +
+ "IdealCore = " + Thread.IdealCore + ", " +
+ "ActualPriority = " + Thread.ActualPriority + ", " +
+ "WantedPriority = " + Thread.WantedPriority + ") " + Message);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ foreach (SchedulerThread SchedThread in AllThreads.Values)
+ {
+ SchedThread.Dispose();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSession.cs b/Ryujinx.HLE/HOS/Kernel/KSession.cs
new file mode 100644
index 00000000..4b21d3a6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSession.cs
@@ -0,0 +1,31 @@
+using Ryujinx.HLE.HOS.Services;
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSession : IDisposable
+ {
+ public IpcService Service { get; private set; }
+
+ public string ServiceName { get; private set; }
+
+ public KSession(IpcService Service, string ServiceName)
+ {
+ this.Service = Service;
+ this.ServiceName = ServiceName;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing && Service is IDisposable DisposableService)
+ {
+ DisposableService.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
new file mode 100644
index 00000000..cdd31667
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSharedMemory
+ {
+ public long PA { get; private set; }
+ public long Size { get; private set; }
+
+ public KSharedMemory(long PA, long Size)
+ {
+ this.PA = PA;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
new file mode 100644
index 00000000..b83b0004
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KSynchronizationObject : IDisposable
+ {
+ public ManualResetEvent WaitEvent { get; private set; }
+
+ public KSynchronizationObject()
+ {
+ WaitEvent = new ManualResetEvent(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ WaitEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs
new file mode 100644
index 00000000..171fefc5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KThread.cs
@@ -0,0 +1,98 @@
+using ChocolArm64;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KThread : KSynchronizationObject
+ {
+ public AThread Thread { get; private set; }
+
+ public int CoreMask { get; set; }
+
+ public long MutexAddress { get; set; }
+ public long CondVarAddress { get; set; }
+ public long ArbiterWaitAddress { get; set; }
+
+ public bool CondVarSignaled { get; set; }
+ public bool ArbiterSignaled { get; set; }
+
+ private Process Process;
+
+ public List<KThread> MutexWaiters { get; private set; }
+
+ public KThread MutexOwner { get; set; }
+
+ public int ActualPriority { get; private set; }
+ public int WantedPriority { get; private set; }
+
+ public int ActualCore { get; set; }
+ public int ProcessorId { get; set; }
+ public int IdealCore { get; set; }
+
+ public int WaitHandle { get; set; }
+
+ public long LastPc { get; set; }
+
+ public int ThreadId { get; private set; }
+
+ public KThread(
+ AThread Thread,
+ Process Process,
+ int ProcessorId,
+ int Priority,
+ int ThreadId)
+ {
+ this.Thread = Thread;
+ this.Process = Process;
+ this.ProcessorId = ProcessorId;
+ this.IdealCore = ProcessorId;
+ this.ThreadId = ThreadId;
+
+ MutexWaiters = new List<KThread>();
+
+ CoreMask = 1 << ProcessorId;
+
+ ActualPriority = WantedPriority = Priority;
+ }
+
+ public void SetPriority(int Priority)
+ {
+ WantedPriority = Priority;
+
+ UpdatePriority();
+ }
+
+ public void UpdatePriority()
+ {
+ bool PriorityChanged;
+
+ lock (Process.ThreadSyncLock)
+ {
+ int OldPriority = ActualPriority;
+
+ int CurrPriority = WantedPriority;
+
+ foreach (KThread Thread in MutexWaiters)
+ {
+ int WantedPriority = Thread.WantedPriority;
+
+ if (CurrPriority > WantedPriority)
+ {
+ CurrPriority = WantedPriority;
+ }
+ }
+
+ PriorityChanged = CurrPriority != OldPriority;
+
+ ActualPriority = CurrPriority;
+ }
+
+ if (PriorityChanged)
+ {
+ Process.Scheduler.Resort(this);
+
+ MutexOwner?.UpdatePriority();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs b/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
new file mode 100644
index 00000000..1fb2ce6a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ 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)
+ {
+ this.PagePosition = PagePosition;
+
+ Slots = new bool[KMemoryManager.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/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
new file mode 100644
index 00000000..6ebffa7e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class KTransferMemory
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+
+ public KTransferMemory(long Position, long Size)
+ {
+ this.Position = Position;
+ this.Size = Size;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs
new file mode 100644
index 00000000..e76ae11e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class KernelErr
+ {
+ public const int InvalidSize = 101;
+ public const int InvalidAddress = 102;
+ public const int OutOfMemory = 104;
+ public const int NoAccessPerm = 106;
+ public const int InvalidPermission = 108;
+ public const int InvalidMemRange = 110;
+ public const int InvalidPriority = 112;
+ public const int InvalidCoreId = 113;
+ public const int InvalidHandle = 114;
+ public const int InvalidMaskValue = 116;
+ public const int Timeout = 117;
+ public const int Canceled = 118;
+ public const int CountOutOfRange = 119;
+ public const int InvalidEnumValue = 120;
+ public const int InvalidThread = 122;
+ public const int InvalidState = 125;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs b/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
new file mode 100644
index 00000000..8f3197cb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [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/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs b/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
new file mode 100644
index 00000000..63539c2e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [Flags]
+ enum MemoryPermission : byte
+ {
+ None = 0,
+ Mask = 0xff,
+
+ Read = 1 << 0,
+ Write = 1 << 1,
+ Execute = 1 << 2,
+
+ ReadAndWrite = Read | Write,
+ ReadAndExecute = Read | Execute
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryState.cs b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs
new file mode 100644
index 00000000..2c37723c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ [Flags]
+ enum MemoryState : uint
+ {
+ Unmapped = 0x00000000,
+ Io = 0x00002001,
+ Normal = 0x00042002,
+ CodeStatic = 0x00DC7E03,
+ CodeMutable = 0x03FEBD04,
+ Heap = 0x037EBD05,
+ SharedMemory = 0x00402006,
+ ModCodeStatic = 0x00DD7E08,
+ ModCodeMutable = 0x03FFBD09,
+ IpcBuffer0 = 0x005C3C0A,
+ MappedMemory = 0x005C3C0B,
+ ThreadLocal = 0x0040200C,
+ TransferMemoryIsolated = 0x015C3C0D,
+ TransferMemory = 0x005C380E,
+ ProcessMemory = 0x0040380F,
+ Reserved = 0x00000010,
+ IpcBuffer1 = 0x005C3811,
+ IpcBuffer3 = 0x004C2812,
+ KernelStack = 0x00002013,
+ CodeReadOnly = 0x00402214,
+ CodeWritable = 0x00402015,
+ 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/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs b/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
new file mode 100644
index 00000000..b8008f75
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/NsTimeConverter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ static class NsTimeConverter
+ {
+ public static int GetTimeMs(ulong Ns)
+ {
+ ulong Ms = Ns / 1_000_000;
+
+ if (Ms < int.MaxValue)
+ {
+ return (int)Ms;
+ }
+ else
+ {
+ return int.MaxValue;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs b/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
new file mode 100644
index 00000000..bab7b03e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SchedulerThread.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class SchedulerThread : IDisposable
+ {
+ public KThread Thread { get; private set; }
+
+ public SchedulerThread Next { get; set; }
+
+ public bool IsActive { get; set; }
+
+ public AutoResetEvent WaitSync { get; private set; }
+ public ManualResetEvent WaitActivity { get; private set; }
+ public AutoResetEvent WaitSched { get; private set; }
+
+ public SchedulerThread(KThread Thread)
+ {
+ this.Thread = Thread;
+
+ IsActive = true;
+
+ WaitSync = new AutoResetEvent(false);
+
+ WaitActivity = new ManualResetEvent(true);
+
+ WaitSched = new AutoResetEvent(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ WaitSync.Dispose();
+
+ WaitActivity.Dispose();
+
+ WaitSched.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
new file mode 100644
index 00000000..fcb72c14
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs
@@ -0,0 +1,123 @@
+using ChocolArm64.Events;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private delegate void SvcFunc(AThreadState ThreadState);
+
+ private Dictionary<int, SvcFunc> SvcFuncs;
+
+ private Switch Device;
+ private Process Process;
+ private AMemory Memory;
+
+ private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
+
+ private const uint SelfThreadHandle = 0xffff8000;
+ private const uint SelfProcessHandle = 0xffff8001;
+
+ private static Random Rng;
+
+ public SvcHandler(Switch Device, Process Process)
+ {
+ SvcFuncs = new Dictionary<int, SvcFunc>()
+ {
+ { 0x01, SvcSetHeapSize },
+ { 0x03, SvcSetMemoryAttribute },
+ { 0x04, SvcMapMemory },
+ { 0x05, SvcUnmapMemory },
+ { 0x06, SvcQueryMemory },
+ { 0x07, SvcExitProcess },
+ { 0x08, SvcCreateThread },
+ { 0x09, SvcStartThread },
+ { 0x0a, SvcExitThread },
+ { 0x0b, SvcSleepThread },
+ { 0x0c, SvcGetThreadPriority },
+ { 0x0d, SvcSetThreadPriority },
+ { 0x0e, SvcGetThreadCoreMask },
+ { 0x0f, SvcSetThreadCoreMask },
+ { 0x10, SvcGetCurrentProcessorNumber },
+ { 0x12, SvcClearEvent },
+ { 0x13, SvcMapSharedMemory },
+ { 0x14, SvcUnmapSharedMemory },
+ { 0x15, SvcCreateTransferMemory },
+ { 0x16, SvcCloseHandle },
+ { 0x17, SvcResetSignal },
+ { 0x18, SvcWaitSynchronization },
+ { 0x19, SvcCancelSynchronization },
+ { 0x1a, SvcArbitrateLock },
+ { 0x1b, SvcArbitrateUnlock },
+ { 0x1c, SvcWaitProcessWideKeyAtomic },
+ { 0x1d, SvcSignalProcessWideKey },
+ { 0x1e, SvcGetSystemTick },
+ { 0x1f, SvcConnectToNamedPort },
+ { 0x21, SvcSendSyncRequest },
+ { 0x22, SvcSendSyncRequestWithUserBuffer },
+ { 0x25, SvcGetThreadId },
+ { 0x26, SvcBreak },
+ { 0x27, SvcOutputDebugString },
+ { 0x29, SvcGetInfo },
+ { 0x2c, SvcMapPhysicalMemory },
+ { 0x2d, SvcUnmapPhysicalMemory },
+ { 0x32, SvcSetThreadActivity },
+ { 0x33, SvcGetThreadContext3 },
+ { 0x34, SvcWaitForAddress }
+ };
+
+ this.Device = Device;
+ this.Process = Process;
+ this.Memory = Process.Memory;
+
+ SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
+ }
+
+ static SvcHandler()
+ {
+ Rng = new Random();
+ }
+
+ public void SvcCall(object sender, AInstExceptionEventArgs e)
+ {
+ AThreadState ThreadState = (AThreadState)sender;
+
+ Process.GetThread(ThreadState.Tpidr).LastPc = e.Position;
+
+ if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
+ {
+ Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
+
+ Func(ThreadState);
+
+ Process.Scheduler.Reschedule(Process.GetThread(ThreadState.Tpidr));
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
+ }
+ else
+ {
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException($"0x{e.Id:x4}");
+ }
+ }
+
+ private KThread GetThread(long Tpidr, int Handle)
+ {
+ if ((uint)Handle == SelfThreadHandle)
+ {
+ return Process.GetThread(Tpidr);
+ }
+ else
+ {
+ return Process.HandleTable.GetData<KThread>(Handle);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
new file mode 100644
index 00000000..b9bca74a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs
@@ -0,0 +1,577 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcSetHeapSize(AThreadState ThreadState)
+ {
+ long Size = (long)ThreadState.X1;
+
+ if ((Size & 0x1fffff) != 0 || Size != (uint)Size)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.TrySetHeapSize(Size, out long Position);
+
+ ThreadState.X0 = (ulong)Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)Position;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+ }
+
+ private void SvcSetMemoryAttribute(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ MemoryAttribute AttributeMask = (MemoryAttribute)ThreadState.X2;
+ MemoryAttribute AttributeValue = (MemoryAttribute)ThreadState.X3;
+
+ MemoryAttribute Attributes = AttributeMask | AttributeValue;
+
+ if (Attributes != AttributeMask ||
+ (Attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.SetMemoryAttribute(
+ Position,
+ Size,
+ AttributeMask,
+ AttributeValue);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+ else
+ {
+ Memory.StopObservingRegion(Position, Size);
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcMapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Src | Dst))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Src, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideNewMapRegion(Dst, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.Map(Src, Dst, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapMemory(AThreadState ThreadState)
+ {
+ long Dst = (long)ThreadState.X0;
+ long Src = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Src | Dst))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Src, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideNewMapRegion(Dst, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.Unmap(Src, Dst, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcQueryMemory(AThreadState ThreadState)
+ {
+ long InfoPtr = (long)ThreadState.X0;
+ long Position = (long)ThreadState.X2;
+
+ KMemoryInfo BlkInfo = Process.MemoryManager.QueryMemory(Position);
+
+ Memory.WriteInt64(InfoPtr + 0x00, BlkInfo.Position);
+ Memory.WriteInt64(InfoPtr + 0x08, BlkInfo.Size);
+ Memory.WriteInt32(InfoPtr + 0x10, (int)BlkInfo.State & 0xff);
+ Memory.WriteInt32(InfoPtr + 0x14, (int)BlkInfo.Attribute);
+ Memory.WriteInt32(InfoPtr + 0x18, (int)BlkInfo.Permission);
+ Memory.WriteInt32(InfoPtr + 0x1c, BlkInfo.IpcRefCount);
+ Memory.WriteInt32(InfoPtr + 0x20, BlkInfo.DeviceRefCount);
+ Memory.WriteInt32(InfoPtr + 0x24, 0);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = 0;
+ }
+
+ private void SvcMapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
+
+ if ((Permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
+
+ return;
+ }
+
+ KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
+
+ if (SharedMemory == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (SharedMemory.Size != Size)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.MapSharedMemory(SharedMemory, Permission, Position);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapSharedMemory(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);
+
+ if (SharedMemory == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.UnmapSharedMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcCreateTransferMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X1;
+ long Size = (long)ThreadState.X2;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ MemoryPermission Permission = (MemoryPermission)ThreadState.X3;
+
+ if (Permission > MemoryPermission.ReadAndWrite || Permission == MemoryPermission.Write)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);
+
+ return;
+ }
+
+ Process.MemoryManager.ReserveTransferMemory(Position, Size, Permission);
+
+ KTransferMemory TransferMemory = new KTransferMemory(Position, Size);
+
+ int Handle = Process.HandleTable.OpenHandle(TransferMemory);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ private void SvcMapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.MapPhysicalMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ if (!PageAligned(Position))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ if (!PageAligned(Size) || Size == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);
+
+ return;
+ }
+
+ if ((ulong)(Position + Size) <= (ulong)Position)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (!InsideAddrSpace(Position, Size))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ long Result = Process.MemoryManager.UnmapPhysicalMemory(Position, Size);
+
+ if (Result != 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
+ }
+
+ ThreadState.X0 = (ulong)Result;
+ }
+
+ private static bool PageAligned(long Position)
+ {
+ return (Position & (KMemoryManager.PageSize - 1)) == 0;
+ }
+
+ private bool InsideAddrSpace(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.AddrSpaceStart &&
+ End < (ulong)Process.MemoryManager.AddrSpaceEnd;
+ }
+
+ private bool InsideMapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.MapRegionStart &&
+ End < (ulong)Process.MemoryManager.MapRegionEnd;
+ }
+
+ private bool InsideHeapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.HeapRegionStart &&
+ End < (ulong)Process.MemoryManager.HeapRegionEnd;
+ }
+
+ private bool InsideNewMapRegion(long Position, long Size)
+ {
+ ulong Start = (ulong)Position;
+ ulong End = (ulong)Size + Start;
+
+ return Start >= (ulong)Process.MemoryManager.NewMapRegionStart &&
+ End < (ulong)Process.MemoryManager.NewMapRegionEnd;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
new file mode 100644
index 00000000..7cc1c858
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs
@@ -0,0 +1,373 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Threading;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int AllowedCpuIdBitmask = 0b1111;
+
+ private const bool EnableProcessDebugging = false;
+
+ private void SvcExitProcess(AThreadState ThreadState)
+ {
+ Device.System.ExitProcess(Process.ProcessId);
+ }
+
+ private void SvcClearEvent(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ //TODO: Implement events.
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcCloseHandle(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ object Obj = Process.HandleTable.CloseHandle(Handle);
+
+ if (Obj == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (Obj is KSession Session)
+ {
+ Session.Dispose();
+ }
+ else if (Obj is KTransferMemory TransferMemory)
+ {
+ Process.MemoryManager.ResetTransferMemory(
+ TransferMemory.Position,
+ TransferMemory.Size);
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcResetSignal(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ KEvent Event = Process.HandleTable.GetData<KEvent>(Handle);
+
+ if (Event != null)
+ {
+ Event.WaitEvent.Reset();
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid event handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcWaitSynchronization(AThreadState ThreadState)
+ {
+ long HandlesPtr = (long)ThreadState.X1;
+ int HandlesCount = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "HandlesPtr = 0x" + HandlesPtr .ToString("x16") + ", " +
+ "HandlesCount = 0x" + HandlesCount.ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout .ToString("x16"));
+
+ if ((uint)HandlesCount > 0x40)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
+
+ for (int Index = 0; Index < HandlesCount; Index++)
+ {
+ int Handle = Memory.ReadInt32(HandlesPtr + Index * 4);
+
+ KSynchronizationObject SyncObj = Process.HandleTable.GetData<KSynchronizationObject>(Handle);
+
+ if (SyncObj == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ Handles[Index] = SyncObj.WaitEvent;
+ }
+
+ using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
+ {
+ if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
+ {
+ throw new InvalidOperationException();
+ }
+
+ Handles[HandlesCount] = WaitEvent;
+
+ Process.Scheduler.Suspend(CurrThread);
+
+ int HandleIndex;
+
+ ulong Result = 0;
+
+ if (Timeout != ulong.MaxValue)
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
+ }
+ else
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles);
+ }
+
+ if (HandleIndex == WaitHandle.WaitTimeout)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+ }
+ else if (HandleIndex == HandlesCount)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
+ }
+
+ SyncWaits.TryRemove(CurrThread, out _);
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)HandleIndex;
+ }
+ }
+ }
+
+ private void SvcCancelSynchronization(AThreadState ThreadState)
+ {
+ int ThreadHandle = (int)ThreadState.X0;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
+ {
+ WaitEvent.Set();
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetSystemTick(AThreadState ThreadState)
+ {
+ ThreadState.X0 = ThreadState.CntpctEl0;
+ }
+
+ private void SvcConnectToNamedPort(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ long NamePtr = (long)ThreadState.X1;
+
+ string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8);
+
+ //TODO: Validate that app has perms to access the service, and that the service
+ //actually exists, return error codes otherwise.
+ KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
+
+ ulong Handle = (ulong)Process.HandleTable.OpenHandle(Session);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = Handle;
+ }
+
+ private void SvcSendSyncRequest(AThreadState ThreadState)
+ {
+ SendSyncRequest(ThreadState, ThreadState.Tpidr, 0x100, (int)ThreadState.X0);
+ }
+
+ private void SvcSendSyncRequestWithUserBuffer(AThreadState ThreadState)
+ {
+ SendSyncRequest(
+ ThreadState,
+ (long)ThreadState.X0,
+ (long)ThreadState.X1,
+ (int)ThreadState.X2);
+ }
+
+ private void SendSyncRequest(AThreadState ThreadState, long CmdPtr, long Size, int Handle)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ byte[] CmdData = Memory.ReadBytes(CmdPtr, Size);
+
+ KSession Session = Process.HandleTable.GetData<KSession>(Handle);
+
+ if (Session != null)
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
+
+ long Result = IpcHandler.IpcCall(Device, Process, Memory, Session, Cmd, CmdPtr);
+
+ Thread.Yield();
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = (ulong)Result;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcBreak(AThreadState ThreadState)
+ {
+ long Reason = (long)ThreadState.X0;
+ long Unknown = (long)ThreadState.X1;
+ long Info = (long)ThreadState.X2;
+
+ Process.PrintStackTrace(ThreadState);
+
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void SvcOutputDebugString(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ long Size = (long)ThreadState.X1;
+
+ string Str = AMemoryHelper.ReadAsciiString(Memory, Position, Size);
+
+ Device.Log.PrintWarning(LogClass.KernelSvc, Str);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetInfo(AThreadState ThreadState)
+ {
+ long StackPtr = (long)ThreadState.X0;
+ int InfoType = (int)ThreadState.X1;
+ long Handle = (long)ThreadState.X2;
+ int InfoId = (int)ThreadState.X3;
+
+ //Fail for info not available on older Kernel versions.
+ if (InfoType == 18 ||
+ InfoType == 19 ||
+ InfoType == 20)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+
+ return;
+ }
+
+ switch (InfoType)
+ {
+ case 0:
+ ThreadState.X1 = AllowedCpuIdBitmask;
+ break;
+
+ case 2:
+ ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionStart;
+ break;
+
+ case 3:
+ ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionEnd -
+ (ulong)Process.MemoryManager.MapRegionStart;
+ break;
+
+ case 4:
+ ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionStart;
+ break;
+
+ case 5:
+ ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionEnd -
+ (ulong)Process.MemoryManager.HeapRegionStart;
+ break;
+
+ case 6:
+ ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalAvailableSize;
+ break;
+
+ case 7:
+ ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalUsedSize;
+ break;
+
+ case 8:
+ ThreadState.X1 = EnableProcessDebugging ? 1 : 0;
+ break;
+
+ case 11:
+ ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32);
+ break;
+
+ case 12:
+ ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceStart;
+ break;
+
+ case 13:
+ ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceEnd -
+ (ulong)Process.MemoryManager.AddrSpaceStart;
+ break;
+
+ case 14:
+ ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionStart;
+ break;
+
+ case 15:
+ ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionEnd -
+ (ulong)Process.MemoryManager.NewMapRegionStart;
+ break;
+
+ case 16:
+ ThreadState.X1 = (ulong)(Process.MetaData?.SystemResourceSize ?? 0);
+ break;
+
+ case 17:
+ ThreadState.X1 = (ulong)Process.MemoryManager.PersonalMmHeapUsage;
+ break;
+
+ default:
+ Process.PrintStackTrace(ThreadState);
+
+ throw new NotImplementedException($"SvcGetInfo: {InfoType} 0x{Handle:x8} {InfoId}");
+ }
+
+ ThreadState.X0 = 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs
new file mode 100644
index 00000000..69e75ec0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs
@@ -0,0 +1,385 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System.Threading;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private void SvcCreateThread(AThreadState ThreadState)
+ {
+ long EntryPoint = (long)ThreadState.X1;
+ long ArgsPtr = (long)ThreadState.X2;
+ long StackTop = (long)ThreadState.X3;
+ int Priority = (int)ThreadState.X4;
+ int ProcessorId = (int)ThreadState.X5;
+
+ if ((uint)Priority > 0x3f)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority);
+
+ return;
+ }
+
+ if (ProcessorId == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ ProcessorId = 0;
+ }
+ else if ((uint)ProcessorId > 3)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+
+ int Handle = Process.MakeThread(
+ EntryPoint,
+ StackTop,
+ ArgsPtr,
+ Priority,
+ ProcessorId);
+
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Handle;
+ }
+
+ private void SvcStartThread(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+
+ KThread NewThread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (NewThread != null)
+ {
+ Process.Scheduler.StartThread(NewThread);
+ Process.Scheduler.SetReschedule(NewThread.ProcessorId);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcExitThread(AThreadState ThreadState)
+ {
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CurrThread.Thread.StopExecution();
+ }
+
+ private void SvcSleepThread(AThreadState ThreadState)
+ {
+ ulong TimeoutNs = ThreadState.X0;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + TimeoutNs.ToString("x16"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (TimeoutNs == 0 || TimeoutNs == ulong.MaxValue)
+ {
+ Process.Scheduler.Yield(CurrThread);
+ }
+ else
+ {
+ Process.Scheduler.Suspend(CurrThread);
+
+ Thread.Sleep(NsTimeConverter.GetTimeMs(TimeoutNs));
+
+ Process.Scheduler.Resume(CurrThread);
+ }
+ }
+
+ private void SvcGetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ActualPriority;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadPriority(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ int Priority = (int)ThreadState.X1;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Handle = 0x" + Handle .ToString("x8") + ", " +
+ "Priority = 0x" + Priority.ToString("x8"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ Thread.SetPriority(Priority);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcGetThreadCoreMask(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle.ToString("x8"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.IdealCore;
+ ThreadState.X2 = (ulong)Thread.CoreMask;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadCoreMask(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ int IdealCore = (int)ThreadState.X1;
+ long CoreMask = (long)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Handle = 0x" + Handle .ToString("x8") + ", " +
+ "IdealCore = 0x" + IdealCore.ToString("x8") + ", " +
+ "CoreMask = 0x" + CoreMask .ToString("x16"));
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (IdealCore == -2)
+ {
+ //TODO: Get this value from the NPDM file.
+ IdealCore = 0;
+
+ CoreMask = 1 << IdealCore;
+ }
+ else
+ {
+ if ((uint)IdealCore > 3)
+ {
+ if ((IdealCore | 2) != -1)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{IdealCore:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId);
+
+ return;
+ }
+ }
+ else if ((CoreMask & (1 << IdealCore)) == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+ }
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ //-1 is used as "don't care", so the IdealCore value is ignored.
+ //-2 is used as "use NPDM default core id" (handled above).
+ //-3 is used as "don't update", the old IdealCore value is kept.
+ if (IdealCore == -3 && (CoreMask & (1 << Thread.IdealCore)) == 0)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{CoreMask:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);
+
+ return;
+ }
+
+ Process.Scheduler.ChangeCore(Thread, IdealCore, (int)CoreMask);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcGetCurrentProcessorNumber(AThreadState ThreadState)
+ {
+ ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).ActualCore;
+ }
+
+ private void SvcGetThreadId(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, Handle);
+
+ if (Thread != null)
+ {
+ ThreadState.X0 = 0;
+ ThreadState.X1 = (ulong)Thread.ThreadId;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcSetThreadActivity(AThreadState ThreadState)
+ {
+ int Handle = (int)ThreadState.X0;
+ bool Active = (int)ThreadState.X1 == 0;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (Thread != null)
+ {
+ Process.Scheduler.SetThreadActivity(Thread, Active);
+
+ ThreadState.X0 = 0;
+ }
+ else
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+ }
+ }
+
+ private void SvcGetThreadContext3(AThreadState ThreadState)
+ {
+ long Position = (long)ThreadState.X0;
+ int Handle = (int)ThreadState.X1;
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(Handle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ if (Process.GetThread(ThreadState.Tpidr) == Thread)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Thread handle 0x{Handle:x8} is current thread!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread);
+
+ return;
+ }
+
+ Memory.WriteUInt64(Position + 0x0, ThreadState.X0);
+ Memory.WriteUInt64(Position + 0x8, ThreadState.X1);
+ Memory.WriteUInt64(Position + 0x10, ThreadState.X2);
+ Memory.WriteUInt64(Position + 0x18, ThreadState.X3);
+ Memory.WriteUInt64(Position + 0x20, ThreadState.X4);
+ Memory.WriteUInt64(Position + 0x28, ThreadState.X5);
+ Memory.WriteUInt64(Position + 0x30, ThreadState.X6);
+ Memory.WriteUInt64(Position + 0x38, ThreadState.X7);
+ Memory.WriteUInt64(Position + 0x40, ThreadState.X8);
+ Memory.WriteUInt64(Position + 0x48, ThreadState.X9);
+ Memory.WriteUInt64(Position + 0x50, ThreadState.X10);
+ Memory.WriteUInt64(Position + 0x58, ThreadState.X11);
+ Memory.WriteUInt64(Position + 0x60, ThreadState.X12);
+ Memory.WriteUInt64(Position + 0x68, ThreadState.X13);
+ Memory.WriteUInt64(Position + 0x70, ThreadState.X14);
+ Memory.WriteUInt64(Position + 0x78, ThreadState.X15);
+ Memory.WriteUInt64(Position + 0x80, ThreadState.X16);
+ Memory.WriteUInt64(Position + 0x88, ThreadState.X17);
+ Memory.WriteUInt64(Position + 0x90, ThreadState.X18);
+ Memory.WriteUInt64(Position + 0x98, ThreadState.X19);
+ Memory.WriteUInt64(Position + 0xa0, ThreadState.X20);
+ Memory.WriteUInt64(Position + 0xa8, ThreadState.X21);
+ Memory.WriteUInt64(Position + 0xb0, ThreadState.X22);
+ Memory.WriteUInt64(Position + 0xb8, ThreadState.X23);
+ Memory.WriteUInt64(Position + 0xc0, ThreadState.X24);
+ Memory.WriteUInt64(Position + 0xc8, ThreadState.X25);
+ Memory.WriteUInt64(Position + 0xd0, ThreadState.X26);
+ Memory.WriteUInt64(Position + 0xd8, ThreadState.X27);
+ Memory.WriteUInt64(Position + 0xe0, ThreadState.X28);
+ Memory.WriteUInt64(Position + 0xe8, ThreadState.X29);
+ Memory.WriteUInt64(Position + 0xf0, ThreadState.X30);
+ Memory.WriteUInt64(Position + 0xf8, ThreadState.X31);
+
+ Memory.WriteInt64(Position + 0x100, Thread.LastPc);
+
+ Memory.WriteUInt64(Position + 0x108, (ulong)ThreadState.Psr);
+
+ Memory.WriteVector128(Position + 0x110, ThreadState.V0);
+ Memory.WriteVector128(Position + 0x120, ThreadState.V1);
+ Memory.WriteVector128(Position + 0x130, ThreadState.V2);
+ Memory.WriteVector128(Position + 0x140, ThreadState.V3);
+ Memory.WriteVector128(Position + 0x150, ThreadState.V4);
+ Memory.WriteVector128(Position + 0x160, ThreadState.V5);
+ Memory.WriteVector128(Position + 0x170, ThreadState.V6);
+ Memory.WriteVector128(Position + 0x180, ThreadState.V7);
+ Memory.WriteVector128(Position + 0x190, ThreadState.V8);
+ Memory.WriteVector128(Position + 0x1a0, ThreadState.V9);
+ Memory.WriteVector128(Position + 0x1b0, ThreadState.V10);
+ Memory.WriteVector128(Position + 0x1c0, ThreadState.V11);
+ Memory.WriteVector128(Position + 0x1d0, ThreadState.V12);
+ Memory.WriteVector128(Position + 0x1e0, ThreadState.V13);
+ Memory.WriteVector128(Position + 0x1f0, ThreadState.V14);
+ Memory.WriteVector128(Position + 0x200, ThreadState.V15);
+ Memory.WriteVector128(Position + 0x210, ThreadState.V16);
+ Memory.WriteVector128(Position + 0x220, ThreadState.V17);
+ Memory.WriteVector128(Position + 0x230, ThreadState.V18);
+ Memory.WriteVector128(Position + 0x240, ThreadState.V19);
+ Memory.WriteVector128(Position + 0x250, ThreadState.V20);
+ Memory.WriteVector128(Position + 0x260, ThreadState.V21);
+ Memory.WriteVector128(Position + 0x270, ThreadState.V22);
+ Memory.WriteVector128(Position + 0x280, ThreadState.V23);
+ Memory.WriteVector128(Position + 0x290, ThreadState.V24);
+ Memory.WriteVector128(Position + 0x2a0, ThreadState.V25);
+ Memory.WriteVector128(Position + 0x2b0, ThreadState.V26);
+ Memory.WriteVector128(Position + 0x2c0, ThreadState.V27);
+ Memory.WriteVector128(Position + 0x2d0, ThreadState.V28);
+ Memory.WriteVector128(Position + 0x2e0, ThreadState.V29);
+ Memory.WriteVector128(Position + 0x2f0, ThreadState.V30);
+ Memory.WriteVector128(Position + 0x300, ThreadState.V31);
+
+ Memory.WriteInt32(Position + 0x310, ThreadState.Fpcr);
+ Memory.WriteInt32(Position + 0x314, ThreadState.Fpsr);
+ Memory.WriteInt64(Position + 0x318, ThreadState.Tpidr);
+
+ ThreadState.X0 = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
new file mode 100644
index 00000000..7097d0f7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs
@@ -0,0 +1,523 @@
+using ChocolArm64.State;
+using Ryujinx.HLE.Logging;
+using System;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ partial class SvcHandler
+ {
+ private const int MutexHasListenersMask = 0x40000000;
+
+ private void SvcArbitrateLock(AThreadState ThreadState)
+ {
+ int OwnerThreadHandle = (int)ThreadState.X0;
+ long MutexAddress = (long)ThreadState.X1;
+ int WaitThreadHandle = (int)ThreadState.X2;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "OwnerThreadHandle = 0x" + OwnerThreadHandle.ToString("x8") + ", " +
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "WaitThreadHandle = 0x" + WaitThreadHandle .ToString("x8"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.HandleTable.GetData<KThread>(WaitThreadHandle);
+
+ if (WaitThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid requesting thread handle 0x{WaitThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ MutexLock(CurrThread, WaitThread, OwnerThreadHandle, WaitThreadHandle, MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcArbitrateUnlock(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + MutexAddress.ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ MutexUnlock(Process.GetThread(ThreadState.Tpidr), MutexAddress);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcWaitProcessWideKeyAtomic(AThreadState ThreadState)
+ {
+ long MutexAddress = (long)ThreadState.X0;
+ long CondVarAddress = (long)ThreadState.X1;
+ int ThreadHandle = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "MutexAddress = 0x" + MutexAddress .ToString("x16") + ", " +
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "ThreadHandle = 0x" + ThreadHandle .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout .ToString("x16"));
+
+ if (IsPointingInsideKernel(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(MutexAddress))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{MutexAddress:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ KThread Thread = Process.HandleTable.GetData<KThread>(ThreadHandle);
+
+ if (Thread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+ return;
+ }
+
+ KThread WaitThread = Process.GetThread(ThreadState.Tpidr);
+
+ if (!CondVarWait(WaitThread, ThreadHandle, MutexAddress, CondVarAddress, Timeout))
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+
+ return;
+ }
+
+ ThreadState.X0 = 0;
+ }
+
+ private void SvcSignalProcessWideKey(AThreadState ThreadState)
+ {
+ long CondVarAddress = (long)ThreadState.X0;
+ int Count = (int)ThreadState.X1;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "CondVarAddress = 0x" + CondVarAddress.ToString("x16") + ", " +
+ "Count = 0x" + Count .ToString("x8"));
+
+ KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
+
+ CondVarSignal(ThreadState, CurrThread, CondVarAddress, Count);
+
+ ThreadState.X0 = 0;
+ }
+
+ private void MutexLock(
+ KThread CurrThread,
+ KThread WaitThread,
+ int OwnerThreadHandle,
+ int WaitThreadHandle,
+ long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
+ {
+ return;
+ }
+
+ CurrThread.WaitHandle = WaitThreadHandle;
+ CurrThread.MutexAddress = MutexAddress;
+
+ InsertWaitingMutexThreadUnsafe(OwnerThreadHandle, WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ Process.Scheduler.EnterWait(CurrThread);
+ }
+
+ private void SvcWaitForAddress(AThreadState ThreadState)
+ {
+ long Address = (long)ThreadState.X0;
+ ArbitrationType Type = (ArbitrationType)ThreadState.X1;
+ int Value = (int)ThreadState.X2;
+ ulong Timeout = ThreadState.X3;
+
+ Device.Log.PrintDebug(LogClass.KernelSvc,
+ "Address = 0x" + Address.ToString("x16") + ", " +
+ "ArbitrationType = 0x" + Type .ToString() + ", " +
+ "Value = 0x" + Value .ToString("x8") + ", " +
+ "Timeout = 0x" + Timeout.ToString("x16"));
+
+ if (IsPointingInsideKernel(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);
+
+ return;
+ }
+
+ if (IsAddressNotWordAligned(Address))
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
+
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+ return;
+ }
+
+ switch (Type)
+ {
+ case ArbitrationType.WaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
+ break;
+
+ case ArbitrationType.DecrementAndWaitIfLessThan:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
+ break;
+
+ case ArbitrationType.WaitIfEqual:
+ ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
+ break;
+
+ default:
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+ break;
+ }
+ }
+
+ private void MutexUnlock(KThread CurrThread, long MutexAddress)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ //This is the new thread that will now own the mutex.
+ //If no threads are waiting for the lock, then it should be null.
+ (KThread OwnerThread, int Count) = PopMutexThreadUnsafe(CurrThread, MutexAddress);
+
+ if (OwnerThread == CurrThread)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (OwnerThread != null)
+ {
+ //Remove all waiting mutex from the old owner,
+ //and insert then on the new owner.
+ UpdateMutexOwnerUnsafe(CurrThread, OwnerThread, MutexAddress);
+
+ CurrThread.UpdatePriority();
+
+ int HasListeners = Count >= 2 ? MutexHasListenersMask : 0;
+
+ Memory.WriteInt32ToSharedAddr(MutexAddress, HasListeners | OwnerThread.WaitHandle);
+
+ OwnerThread.WaitHandle = 0;
+ OwnerThread.MutexAddress = 0;
+ OwnerThread.CondVarAddress = 0;
+ OwnerThread.MutexOwner = null;
+
+ OwnerThread.UpdatePriority();
+
+ Process.Scheduler.WakeUp(OwnerThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Gave mutex to thread id " + OwnerThread.ThreadId + "!");
+ }
+ else
+ {
+ Memory.WriteInt32ToSharedAddr(MutexAddress, 0);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "No threads waiting mutex!");
+ }
+ }
+ }
+
+ private bool CondVarWait(
+ KThread WaitThread,
+ int WaitThreadHandle,
+ long MutexAddress,
+ long CondVarAddress,
+ ulong Timeout)
+ {
+ WaitThread.WaitHandle = WaitThreadHandle;
+ WaitThread.MutexAddress = MutexAddress;
+ WaitThread.CondVarAddress = CondVarAddress;
+
+ lock (Process.ThreadSyncLock)
+ {
+ MutexUnlock(WaitThread, MutexAddress);
+
+ WaitThread.CondVarSignaled = false;
+
+ Process.ThreadArbiterList.Add(WaitThread);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
+ if (Timeout != ulong.MaxValue)
+ {
+ Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
+
+ lock (Process.ThreadSyncLock)
+ {
+ if (!WaitThread.CondVarSignaled || WaitThread.MutexOwner != null)
+ {
+ if (WaitThread.MutexOwner != null)
+ {
+ WaitThread.MutexOwner.MutexWaiters.Remove(WaitThread);
+ WaitThread.MutexOwner.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+ }
+
+ Process.ThreadArbiterList.Remove(WaitThread);
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "Timed out...");
+
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Process.Scheduler.EnterWait(WaitThread);
+ }
+
+ return true;
+ }
+
+ private void CondVarSignal(
+ AThreadState ThreadState,
+ KThread CurrThread,
+ long CondVarAddress,
+ int Count)
+ {
+ lock (Process.ThreadSyncLock)
+ {
+ while (Count == -1 || Count-- > 0)
+ {
+ KThread WaitThread = PopCondVarThreadUnsafe(CondVarAddress);
+
+ if (WaitThread == null)
+ {
+ Device.Log.PrintDebug(LogClass.KernelSvc, "No more threads to wake up!");
+
+ break;
+ }
+
+ WaitThread.CondVarSignaled = true;
+
+ long MutexAddress = WaitThread.MutexAddress;
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ int MutexValue = Memory.ReadInt32(MutexAddress);
+
+ while (MutexValue != 0)
+ {
+ if (Memory.TestExclusive(ThreadState, MutexAddress))
+ {
+ //Wait until the lock is released.
+ InsertWaitingMutexThreadUnsafe(MutexValue & ~MutexHasListenersMask, WaitThread);
+
+ Memory.WriteInt32(MutexAddress, MutexValue | MutexHasListenersMask);
+
+ Memory.ClearExclusiveForStore(ThreadState);
+
+ break;
+ }
+
+ Memory.SetExclusive(ThreadState, MutexAddress);
+
+ MutexValue = Memory.ReadInt32(MutexAddress);
+ }
+
+ Device.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = 0x" + MutexValue.ToString("x8"));
+
+ if (MutexValue == 0)
+ {
+ //Give the lock to this thread.
+ Memory.WriteInt32ToSharedAddr(MutexAddress, WaitThread.WaitHandle);
+
+ WaitThread.WaitHandle = 0;
+ WaitThread.MutexAddress = 0;
+ WaitThread.CondVarAddress = 0;
+
+ WaitThread.MutexOwner?.UpdatePriority();
+
+ WaitThread.MutexOwner = null;
+
+ Process.Scheduler.WakeUp(WaitThread);
+ }
+ }
+ }
+ }
+
+ private void UpdateMutexOwnerUnsafe(KThread CurrThread, KThread NewOwner, long MutexAddress)
+ {
+ //Go through all threads waiting for the mutex,
+ //and update the MutexOwner field to point to the new owner.
+ for (int Index = 0; Index < CurrThread.MutexWaiters.Count; Index++)
+ {
+ KThread Thread = CurrThread.MutexWaiters[Index];
+
+ if (Thread.MutexAddress == MutexAddress)
+ {
+ CurrThread.MutexWaiters.RemoveAt(Index--);
+
+ InsertWaitingMutexThreadUnsafe(NewOwner, Thread);
+ }
+ }
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(int OwnerThreadHandle, KThread WaitThread)
+ {
+ KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
+
+ if (OwnerThread == null)
+ {
+ Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{OwnerThreadHandle:x8}!");
+
+ return;
+ }
+
+ InsertWaitingMutexThreadUnsafe(OwnerThread, WaitThread);
+ }
+
+ private void InsertWaitingMutexThreadUnsafe(KThread OwnerThread, KThread WaitThread)
+ {
+ WaitThread.MutexOwner = OwnerThread;
+
+ if (!OwnerThread.MutexWaiters.Contains(WaitThread))
+ {
+ OwnerThread.MutexWaiters.Add(WaitThread);
+
+ OwnerThread.UpdatePriority();
+ }
+ }
+
+ private (KThread, int) PopMutexThreadUnsafe(KThread OwnerThread, long MutexAddress)
+ {
+ int Count = 0;
+
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in OwnerThread.MutexWaiters)
+ {
+ if (Thread.MutexAddress != MutexAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+
+ Count++;
+ }
+
+ if (WakeThread != null)
+ {
+ OwnerThread.MutexWaiters.Remove(WakeThread);
+ }
+
+ return (WakeThread, Count);
+ }
+
+ private KThread PopCondVarThreadUnsafe(long CondVarAddress)
+ {
+ KThread WakeThread = null;
+
+ foreach (KThread Thread in Process.ThreadArbiterList)
+ {
+ if (Thread.CondVarAddress != CondVarAddress)
+ {
+ continue;
+ }
+
+ if (WakeThread == null || Thread.ActualPriority < WakeThread.ActualPriority)
+ {
+ WakeThread = Thread;
+ }
+ }
+
+ if (WakeThread != null)
+ {
+ Process.ThreadArbiterList.Remove(WakeThread);
+ }
+
+ return WakeThread;
+ }
+
+ private bool IsPointingInsideKernel(long Address)
+ {
+ return ((ulong)Address + 0x1000000000) < 0xffffff000;
+ }
+
+ private bool IsAddressNotWordAligned(long Address)
+ {
+ return (Address & 3) != 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs b/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
new file mode 100644
index 00000000..815e86ad
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Kernel/ThreadQueue.cs
@@ -0,0 +1,158 @@
+namespace Ryujinx.HLE.HOS.Kernel
+{
+ class ThreadQueue
+ {
+ private const int LowestPriority = 0x3f;
+
+ private SchedulerThread Head;
+
+ private object ListLock;
+
+ public ThreadQueue()
+ {
+ ListLock = new object();
+ }
+
+ public void Push(SchedulerThread Wait)
+ {
+ lock (ListLock)
+ {
+ //Ensure that we're not creating circular references
+ //by adding a thread that is already on the list.
+ if (HasThread(Wait))
+ {
+ return;
+ }
+
+ if (Head == null || Head.Thread.ActualPriority >= Wait.Thread.ActualPriority)
+ {
+ Wait.Next = Head;
+
+ Head = Wait;
+
+ return;
+ }
+
+ SchedulerThread Curr = Head;
+
+ while (Curr.Next != null)
+ {
+ if (Curr.Next.Thread.ActualPriority >= Wait.Thread.ActualPriority)
+ {
+ break;
+ }
+
+ Curr = Curr.Next;
+ }
+
+ Wait.Next = Curr.Next;
+ Curr.Next = Wait;
+ }
+ }
+
+ public SchedulerThread Pop(int Core, int MinPriority = LowestPriority)
+ {
+ lock (ListLock)
+ {
+ int CoreMask = 1 << Core;
+
+ SchedulerThread Prev = null;
+ SchedulerThread Curr = Head;
+
+ while (Curr != null)
+ {
+ KThread Thread = Curr.Thread;
+
+ if (Thread.ActualPriority <= MinPriority && (Thread.CoreMask & CoreMask) != 0)
+ {
+ if (Prev != null)
+ {
+ Prev.Next = Curr.Next;
+ }
+ else
+ {
+ Head = Head.Next;
+ }
+
+ break;
+ }
+
+ Prev = Curr;
+ Curr = Curr.Next;
+ }
+
+ return Curr;
+ }
+ }
+
+ public bool Remove(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ if (Head == null)
+ {
+ return false;
+ }
+ else if (Head == Thread)
+ {
+ Head = Head.Next;
+
+ return true;
+ }
+
+ SchedulerThread Prev = Head;
+ SchedulerThread Curr = Head.Next;
+
+ while (Curr != null)
+ {
+ if (Curr == Thread)
+ {
+ Prev.Next = Curr.Next;
+
+ return true;
+ }
+
+ Prev = Curr;
+ Curr = Curr.Next;
+ }
+
+ return false;
+ }
+ }
+
+ public bool Resort(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ if (Remove(Thread))
+ {
+ Push(Thread);
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public bool HasThread(SchedulerThread Thread)
+ {
+ lock (ListLock)
+ {
+ SchedulerThread Curr = Head;
+
+ while (Curr != null)
+ {
+ if (Curr == Thread)
+ {
+ return true;
+ }
+
+ Curr = Curr.Next;
+ }
+
+ return false;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs
new file mode 100644
index 00000000..bfda93f4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Process.cs
@@ -0,0 +1,438 @@
+using ChocolArm64;
+using ChocolArm64.Events;
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Diagnostics;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Nv;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Loaders;
+using Ryujinx.HLE.Loaders.Executables;
+using Ryujinx.HLE.Loaders.Npdm;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS
+{
+ class Process : IDisposable
+ {
+ private const int TickFreq = 19_200_000;
+
+ public Switch Device { get; private set; }
+
+ public bool NeedsHbAbi { get; private set; }
+
+ public long HbAbiDataPosition { get; private set; }
+
+ public int ProcessId { get; private set; }
+
+ private ATranslator Translator;
+
+ public AMemory Memory { get; private set; }
+
+ public KMemoryManager MemoryManager { get; private set; }
+
+ private List<KTlsPageManager> TlsPages;
+
+ public KProcessScheduler Scheduler { get; private set; }
+
+ public List<KThread> ThreadArbiterList { get; private set; }
+
+ public object ThreadSyncLock { get; private set; }
+
+ public Npdm MetaData { get; private set; }
+
+ public KProcessHandleTable HandleTable { get; private set; }
+
+ public AppletStateMgr AppletState { get; private set; }
+
+ private SvcHandler SvcHandler;
+
+ private ConcurrentDictionary<long, KThread> Threads;
+
+ private List<Executable> Executables;
+
+ private Dictionary<long, string> SymbolTable;
+
+ private long ImageBase;
+
+ private bool ShouldDispose;
+
+ private bool Disposed;
+
+ public Process(Switch Device, KProcessScheduler Scheduler, int ProcessId, Npdm MetaData)
+ {
+ this.Device = Device;
+ this.Scheduler = Scheduler;
+ this.MetaData = MetaData;
+ this.ProcessId = ProcessId;
+
+ Memory = new AMemory(Device.Memory.RamPointer);
+
+ MemoryManager = new KMemoryManager(this);
+
+ TlsPages = new List<KTlsPageManager>();
+
+ ThreadArbiterList = new List<KThread>();
+
+ ThreadSyncLock = new object();
+
+ HandleTable = new KProcessHandleTable();
+
+ AppletState = new AppletStateMgr();
+
+ SvcHandler = new SvcHandler(Device, this);
+
+ Threads = new ConcurrentDictionary<long, KThread>();
+
+ Executables = new List<Executable>();
+
+ ImageBase = MemoryManager.CodeRegionStart;
+ }
+
+ public void LoadProgram(IExecutable Program)
+ {
+ if (Disposed)
+ {
+ throw new ObjectDisposedException(nameof(Process));
+ }
+
+ Device.Log.PrintInfo(LogClass.Loader, $"Image base at 0x{ImageBase:x16}.");
+
+ Executable Executable = new Executable(Program, MemoryManager, Memory, ImageBase);
+
+ Executables.Add(Executable);
+
+ ImageBase = IntUtils.AlignUp(Executable.ImageEnd, KMemoryManager.PageSize);
+ }
+
+ public void SetEmptyArgs()
+ {
+ //TODO: This should be part of Run.
+ ImageBase += KMemoryManager.PageSize;
+ }
+
+ public bool Run(bool NeedsHbAbi = false)
+ {
+ if (Disposed)
+ {
+ throw new ObjectDisposedException(nameof(Process));
+ }
+
+ this.NeedsHbAbi = NeedsHbAbi;
+
+ if (Executables.Count == 0)
+ {
+ return false;
+ }
+
+ MakeSymbolTable();
+
+ long MainStackTop = MemoryManager.CodeRegionEnd - KMemoryManager.PageSize;
+
+ long MainStackSize = 1 * 1024 * 1024;
+
+ long MainStackBottom = MainStackTop - MainStackSize;
+
+ MemoryManager.HleMapCustom(
+ MainStackBottom,
+ MainStackSize,
+ MemoryState.MappedMemory,
+ MemoryPermission.ReadAndWrite);
+
+ int Handle = MakeThread(Executables[0].ImageBase, MainStackTop, 0, 44, 0);
+
+ if (Handle == -1)
+ {
+ return false;
+ }
+
+ KThread MainThread = HandleTable.GetData<KThread>(Handle);
+
+ if (NeedsHbAbi)
+ {
+ HbAbiDataPosition = IntUtils.AlignUp(Executables[0].ImageEnd, KMemoryManager.PageSize);
+
+ const long HbAbiDataSize = KMemoryManager.PageSize;
+
+ MemoryManager.HleMapCustom(
+ HbAbiDataPosition,
+ HbAbiDataSize,
+ MemoryState.MappedMemory,
+ MemoryPermission.ReadAndWrite);
+
+ string SwitchPath = Device.FileSystem.SystemPathToSwitchPath(Executables[0].FilePath);
+
+ Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle, SwitchPath);
+
+ MainThread.Thread.ThreadState.X0 = (ulong)HbAbiDataPosition;
+ MainThread.Thread.ThreadState.X1 = ulong.MaxValue;
+ }
+
+ Scheduler.StartThread(MainThread);
+
+ return true;
+ }
+
+ public int MakeThread(
+ long EntryPoint,
+ long StackTop,
+ long ArgsPtr,
+ int Priority,
+ int ProcessorId)
+ {
+ if (Disposed)
+ {
+ throw new ObjectDisposedException(nameof(Process));
+ }
+
+ AThread CpuThread = new AThread(GetTranslator(), Memory, EntryPoint);
+
+ long Tpidr = GetFreeTls();
+
+ int ThreadId = (int)((Tpidr - MemoryManager.TlsIoRegionStart) / 0x200) + 1;
+
+ KThread Thread = new KThread(CpuThread, this, ProcessorId, Priority, ThreadId);
+
+ Thread.LastPc = EntryPoint;
+
+ int Handle = HandleTable.OpenHandle(Thread);
+
+ CpuThread.ThreadState.CntfrqEl0 = TickFreq;
+ CpuThread.ThreadState.Tpidr = Tpidr;
+
+ CpuThread.ThreadState.X0 = (ulong)ArgsPtr;
+ CpuThread.ThreadState.X1 = (ulong)Handle;
+ CpuThread.ThreadState.X31 = (ulong)StackTop;
+
+ CpuThread.ThreadState.Break += BreakHandler;
+ CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall;
+ CpuThread.ThreadState.Undefined += UndefinedHandler;
+
+ CpuThread.WorkFinished += ThreadFinished;
+
+ Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread);
+
+ return Handle;
+ }
+
+ private long GetFreeTls()
+ {
+ long Position;
+
+ lock (TlsPages)
+ {
+ for (int Index = 0; Index < TlsPages.Count; Index++)
+ {
+ if (TlsPages[Index].TryGetFreeTlsAddr(out Position))
+ {
+ return Position;
+ }
+ }
+
+ long PagePosition = MemoryManager.HleMapTlsPage();
+
+ KTlsPageManager TlsPage = new KTlsPageManager(PagePosition);
+
+ TlsPages.Add(TlsPage);
+
+ TlsPage.TryGetFreeTlsAddr(out Position);
+ }
+
+ return Position;
+ }
+
+ private void BreakHandler(object sender, AInstExceptionEventArgs e)
+ {
+ throw new GuestBrokeExecutionException();
+ }
+
+ private void UndefinedHandler(object sender, AInstUndefinedEventArgs e)
+ {
+ throw new UndefinedInstructionException(e.Position, e.RawOpCode);
+ }
+
+ private void MakeSymbolTable()
+ {
+ SymbolTable = new Dictionary<long, string>();
+
+ foreach (Executable Exe in Executables)
+ {
+ foreach (KeyValuePair<long, string> KV in Exe.SymbolTable)
+ {
+ SymbolTable.TryAdd(Exe.ImageBase + KV.Key, KV.Value);
+ }
+ }
+ }
+
+ private ATranslator GetTranslator()
+ {
+ if (Translator == null)
+ {
+ Translator = new ATranslator(SymbolTable);
+
+ Translator.CpuTrace += CpuTraceHandler;
+ }
+
+ return Translator;
+ }
+
+ public void EnableCpuTracing()
+ {
+ Translator.EnableCpuTrace = true;
+ }
+
+ public void DisableCpuTracing()
+ {
+ Translator.EnableCpuTrace = false;
+ }
+
+ private void CpuTraceHandler(object sender, ACpuTraceEventArgs e)
+ {
+ string NsoName = string.Empty;
+
+ for (int Index = Executables.Count - 1; Index >= 0; Index--)
+ {
+ if (e.Position >= Executables[Index].ImageBase)
+ {
+ NsoName = $"{(e.Position - Executables[Index].ImageBase):x16}";
+
+ break;
+ }
+ }
+
+ Device.Log.PrintDebug(LogClass.Cpu, $"Executing at 0x{e.Position:x16} {e.SubName} {NsoName}");
+ }
+
+ public void PrintStackTrace(AThreadState ThreadState)
+ {
+ long[] Positions = ThreadState.GetCallStack();
+
+ StringBuilder Trace = new StringBuilder();
+
+ Trace.AppendLine("Guest stack trace:");
+
+ foreach (long Position in Positions)
+ {
+ if (!SymbolTable.TryGetValue(Position, out string SubName))
+ {
+ SubName = $"Sub{Position:x16}";
+ }
+ else if (SubName.StartsWith("_Z"))
+ {
+ SubName = Demangler.Parse(SubName);
+ }
+
+ Trace.AppendLine(" " + SubName + " (" + GetNsoNameAndAddress(Position) + ")");
+ }
+
+ Device.Log.PrintInfo(LogClass.Cpu, Trace.ToString());
+ }
+
+ private string GetNsoNameAndAddress(long Position)
+ {
+ string Name = string.Empty;
+
+ for (int Index = Executables.Count - 1; Index >= 0; Index--)
+ {
+ if (Position >= Executables[Index].ImageBase)
+ {
+ long Offset = Position - Executables[Index].ImageBase;
+
+ Name = $"{Executables[Index].Name}:{Offset:x8}";
+
+ break;
+ }
+ }
+
+ return Name;
+ }
+
+ private void ThreadFinished(object sender, EventArgs e)
+ {
+ if (sender is AThread Thread)
+ {
+ Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread);
+
+ Scheduler.RemoveThread(KernelThread);
+
+ KernelThread.WaitEvent.Set();
+ }
+
+ if (Threads.Count == 0)
+ {
+ Device.System.ExitProcess(ProcessId);
+ }
+ }
+
+ public KThread GetThread(long Tpidr)
+ {
+ if (!Threads.TryGetValue(Tpidr, out KThread Thread))
+ {
+ throw new InvalidOperationException();
+ }
+
+ return Thread;
+ }
+
+ private void Unload()
+ {
+ if (Disposed || Threads.Count > 0)
+ {
+ return;
+ }
+
+ Disposed = true;
+
+ foreach (object Obj in HandleTable.Clear())
+ {
+ if (Obj is KSession Session)
+ {
+ Session.Dispose();
+ }
+ }
+
+ INvDrvServices.UnloadProcess(this);
+
+ AppletState.Dispose();
+
+ if (NeedsHbAbi && Executables.Count > 0 && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix))
+ {
+ File.Delete(Executables[0].FilePath);
+ }
+
+ Device.Log.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting...");
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ if (Threads.Count > 0)
+ {
+ foreach (KThread Thread in Threads.Values)
+ {
+ Thread.Thread.StopExecution();
+
+ Scheduler.ForceWakeUp(Thread);
+ }
+ }
+ else
+ {
+ Unload();
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ServiceCtx.cs b/Ryujinx.HLE/HOS/ServiceCtx.cs
new file mode 100644
index 00000000..d8c9fdf6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/ServiceCtx.cs
@@ -0,0 +1,39 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS
+{
+ class ServiceCtx
+ {
+ public Switch Device { get; private set; }
+ public Process Process { get; private set; }
+ public AMemory Memory { get; private set; }
+ public KSession Session { get; private set; }
+ public IpcMessage Request { get; private set; }
+ public IpcMessage Response { get; private set; }
+ public BinaryReader RequestData { get; private set; }
+ public BinaryWriter ResponseData { get; private set; }
+
+ public ServiceCtx(
+ Switch Device,
+ Process Process,
+ AMemory Memory,
+ KSession Session,
+ IpcMessage Request,
+ IpcMessage Response,
+ BinaryReader RequestData,
+ BinaryWriter ResponseData)
+ {
+ this.Device = Device;
+ this.Process = Process;
+ this.Memory = Memory;
+ this.Session = Session;
+ this.Request = Request;
+ this.Response = Response;
+ this.RequestData = RequestData;
+ this.ResponseData = ResponseData;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Acc/AccErr.cs b/Ryujinx.HLE/HOS/Services/Acc/AccErr.cs
new file mode 100644
index 00000000..144d6680
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Acc/AccErr.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Acc
+{
+ static class AccErr
+ {
+ public const int UserNotFound = 100;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs
new file mode 100644
index 00000000..36dfd9ef
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Acc/IAccountServiceForApplication.cs
@@ -0,0 +1,125 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Acc
+{
+ class IAccountServiceForApplication : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAccountServiceForApplication()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetUserCount },
+ { 1, GetUserExistence },
+ { 2, ListAllUsers },
+ { 3, ListOpenUsers },
+ { 4, GetLastOpenedUser },
+ { 5, GetProfile },
+ { 100, InitializeApplicationInfo },
+ { 101, GetBaasAccountManagerForApplication }
+ };
+ }
+
+ public long GetUserCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(Context.Device.System.State.GetUserCount());
+
+ return 0;
+ }
+
+ public long GetUserExistence(ServiceCtx Context)
+ {
+ UserId Uuid = new UserId(
+ Context.RequestData.ReadInt64(),
+ Context.RequestData.ReadInt64());
+
+ Context.ResponseData.Write(Context.Device.System.State.TryGetUser(Uuid, out _) ? 1 : 0);
+
+ return 0;
+ }
+
+ public long ListAllUsers(ServiceCtx Context)
+ {
+ return WriteUserList(Context, Context.Device.System.State.GetAllUsers());
+ }
+
+ public long ListOpenUsers(ServiceCtx Context)
+ {
+ return WriteUserList(Context, Context.Device.System.State.GetOpenUsers());
+ }
+
+ private long WriteUserList(ServiceCtx Context, IEnumerable<UserProfile> Profiles)
+ {
+ long OutputPosition = Context.Request.RecvListBuff[0].Position;
+ long OutputSize = Context.Request.RecvListBuff[0].Size;
+
+ long Offset = 0;
+
+ foreach (UserProfile Profile in Profiles)
+ {
+ if ((ulong)Offset + 16 > (ulong)OutputSize)
+ {
+ break;
+ }
+
+ byte[] Uuid = Profile.Uuid.Bytes;
+
+ for (int Index = Uuid.Length - 1; Index >= 0; Index--)
+ {
+ Context.Memory.WriteByte(OutputPosition + Offset++, Uuid[Index]);
+ }
+ }
+
+ return 0;
+ }
+
+ public long GetLastOpenedUser(ServiceCtx Context)
+ {
+ UserProfile LastOpened = Context.Device.System.State.LastOpenUser;
+
+ LastOpened.Uuid.Write(Context.ResponseData);
+
+ return 0;
+ }
+
+ public long GetProfile(ServiceCtx Context)
+ {
+ UserId Uuid = new UserId(
+ Context.RequestData.ReadInt64(),
+ Context.RequestData.ReadInt64());
+
+ if (!Context.Device.System.State.TryGetUser(Uuid, out UserProfile Profile))
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceAcc, $"User 0x{Uuid} not found!");
+
+ return MakeError(ErrorModule.Account, AccErr.UserNotFound);
+ }
+
+ MakeObject(Context, new IProfile(Profile));
+
+ return 0;
+ }
+
+ public long InitializeApplicationInfo(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetBaasAccountManagerForApplication(ServiceCtx Context)
+ {
+ MakeObject(Context, new IManagerForApplication());
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs b/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs
new file mode 100644
index 00000000..813a1b17
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Acc/IManagerForApplication.cs
@@ -0,0 +1,38 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Acc
+{
+ class IManagerForApplication : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManagerForApplication()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CheckAvailability },
+ { 1, GetAccountId }
+ };
+ }
+
+ public long CheckAvailability(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetAccountId(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
+
+ Context.ResponseData.Write(0xcafeL);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs
new file mode 100644
index 00000000..960cc49c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Acc/IProfile.cs
@@ -0,0 +1,58 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Acc
+{
+ class IProfile : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private UserProfile Profile;
+
+ public IProfile(UserProfile Profile)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Get },
+ { 1, GetBase }
+ };
+
+ this.Profile = Profile;
+ }
+
+ public long Get(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
+
+ long Position = Context.Request.ReceiveBuff[0].Position;
+
+ AMemoryHelper.FillWithZeros(Context.Memory, Position, 0x80);
+
+ Context.Memory.WriteInt32(Position, 0);
+ Context.Memory.WriteInt32(Position + 4, 1);
+ Context.Memory.WriteInt64(Position + 8, 1);
+
+ return GetBase(Context);
+ }
+
+ public long GetBase(ServiceCtx Context)
+ {
+ Profile.Uuid.Write(Context.ResponseData);
+
+ Context.ResponseData.Write(Profile.LastModifiedTimestamp);
+
+ byte[] Username = StringUtils.GetFixedLengthBytes(Profile.Name, 0x20, Encoding.UTF8);
+
+ Context.ResponseData.Write(Username);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/AmErr.cs b/Ryujinx.HLE/HOS/Services/Am/AmErr.cs
new file mode 100644
index 00000000..4e7771b8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/AmErr.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ static class AmErr
+ {
+ public const int NoMessages = 3;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/FocusState.cs b/Ryujinx.HLE/HOS/Services/Am/FocusState.cs
new file mode 100644
index 00000000..e8ae7223
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/FocusState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ enum FocusState
+ {
+ InFocus = 1,
+ OutOfFocus = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IAllSystemAppletProxiesService.cs b/Ryujinx.HLE/HOS/Services/Am/IAllSystemAppletProxiesService.cs
new file mode 100644
index 00000000..2d44526a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IAllSystemAppletProxiesService.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IAllSystemAppletProxiesService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAllSystemAppletProxiesService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 100, OpenSystemAppletProxy }
+ };
+ }
+
+ public long OpenSystemAppletProxy(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemAppletProxy());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs
new file mode 100644
index 00000000..c5ed09f5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationCreator.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IApplicationCreator : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationCreator()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs
new file mode 100644
index 00000000..0a10d2a6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationFunctions.cs
@@ -0,0 +1,117 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IApplicationFunctions : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationFunctions()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, PopLaunchParameter },
+ { 20, EnsureSaveData },
+ { 21, GetDesiredLanguage },
+ { 22, SetTerminateResult },
+ { 23, GetDisplayVersion },
+ { 40, NotifyRunning },
+ { 50, GetPseudoDeviceId },
+ { 66, InitializeGamePlayRecording },
+ { 67, SetGamePlayRecordingState }
+ };
+ }
+
+ public long PopLaunchParameter(ServiceCtx Context)
+ {
+ //Only the first 0x18 bytes of the Data seems to be actually used.
+ MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams()));
+
+ return 0;
+ }
+
+ public long EnsureSaveData(ServiceCtx Context)
+ {
+ long UIdLow = Context.RequestData.ReadInt64();
+ long UIdHigh = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long GetDesiredLanguage(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(Context.Device.System.State.DesiredLanguageCode);
+
+ return 0;
+ }
+
+ public long SetTerminateResult(ServiceCtx Context)
+ {
+ int ErrorCode = Context.RequestData.ReadInt32();
+
+ string Result = GetFormattedErrorCode(ErrorCode);
+
+ Context.Device.Log.PrintInfo(LogClass.ServiceAm, $"Result = 0x{ErrorCode:x8} ({Result}).");
+
+ return 0;
+ }
+
+ private string GetFormattedErrorCode(int ErrorCode)
+ {
+ int Module = (ErrorCode >> 0) & 0x1ff;
+ int Description = (ErrorCode >> 9) & 0x1fff;
+
+ return $"{(2000 + Module):d4}-{Description:d4}";
+ }
+
+ public long GetDisplayVersion(ServiceCtx Context)
+ {
+ //FIXME: Need to check correct version on a switch.
+ Context.ResponseData.Write(1L);
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long NotifyRunning(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1);
+
+ return 0;
+ }
+
+ public long GetPseudoDeviceId(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long InitializeGamePlayRecording(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetGamePlayRecordingState(ServiceCtx Context)
+ {
+ int State = Context.RequestData.ReadInt32();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs
new file mode 100644
index 00000000..4003f151
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxy.cs
@@ -0,0 +1,83 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IApplicationProxy : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationProxy()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCommonStateGetter },
+ { 1, GetSelfController },
+ { 2, GetWindowController },
+ { 3, GetAudioController },
+ { 4, GetDisplayController },
+ { 11, GetLibraryAppletCreator },
+ { 20, GetApplicationFunctions },
+ { 1000, GetDebugFunctions }
+ };
+ }
+
+ public long GetCommonStateGetter(ServiceCtx Context)
+ {
+ MakeObject(Context, new ICommonStateGetter());
+
+ return 0;
+ }
+
+ public long GetSelfController(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISelfController());
+
+ return 0;
+ }
+
+ public long GetWindowController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IWindowController());
+
+ return 0;
+ }
+
+ public long GetAudioController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAudioController());
+
+ return 0;
+ }
+
+ public long GetDisplayController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDisplayController());
+
+ return 0;
+ }
+
+ public long GetLibraryAppletCreator(ServiceCtx Context)
+ {
+ MakeObject(Context, new ILibraryAppletCreator());
+
+ return 0;
+ }
+
+ public long GetApplicationFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IApplicationFunctions());
+
+ return 0;
+ }
+
+ public long GetDebugFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDebugFunctions());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs
new file mode 100644
index 00000000..fb518af9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IApplicationProxyService.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IApplicationProxyService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationProxyService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, OpenApplicationProxy }
+ };
+ }
+
+ public long OpenApplicationProxy(ServiceCtx Context)
+ {
+ MakeObject(Context, new IApplicationProxy());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs b/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs
new file mode 100644
index 00000000..8968ad72
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IAudioController.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IAudioController : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, SetExpectedMasterVolume },
+ { 1, GetMainAppletExpectedMasterVolume },
+ { 2, GetLibraryAppletExpectedMasterVolume },
+ { 3, ChangeMainAppletMasterVolume },
+ { 4, SetTransparentVolumeRate }
+ };
+ }
+
+ public long SetExpectedMasterVolume(ServiceCtx Context)
+ {
+ float AppletVolume = Context.RequestData.ReadSingle();
+ float LibraryAppletVolume = Context.RequestData.ReadSingle();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetMainAppletExpectedMasterVolume(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1f);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetLibraryAppletExpectedMasterVolume(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1f);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ChangeMainAppletMasterVolume(ServiceCtx Context)
+ {
+ float Unknown0 = Context.RequestData.ReadSingle();
+ long Unknown1 = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetTransparentVolumeRate(ServiceCtx Context)
+ {
+ float Unknown0 = Context.RequestData.ReadSingle();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs
new file mode 100644
index 00000000..3cdfbbdb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs
@@ -0,0 +1,115 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class ICommonStateGetter : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent DisplayResolutionChangeEvent;
+
+ public ICommonStateGetter()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetEventHandle },
+ { 1, ReceiveMessage },
+ { 5, GetOperationMode },
+ { 6, GetPerformanceMode },
+ { 8, GetBootMode },
+ { 9, GetCurrentFocusState },
+ { 60, GetDefaultDisplayResolution },
+ { 61, GetDefaultDisplayResolutionChangeEvent }
+ };
+
+ DisplayResolutionChangeEvent = new KEvent();
+ }
+
+ public long GetEventHandle(ServiceCtx Context)
+ {
+ KEvent Event = Context.Process.AppletState.MessageEvent;
+
+ int Handle = Context.Process.HandleTable.OpenHandle(Event);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long ReceiveMessage(ServiceCtx Context)
+ {
+ if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message))
+ {
+ return MakeError(ErrorModule.Am, AmErr.NoMessages);
+ }
+
+ Context.ResponseData.Write((int)Message);
+
+ return 0;
+ }
+
+ public long GetOperationMode(ServiceCtx Context)
+ {
+ OperationMode Mode = Context.Device.System.State.DockedMode
+ ? OperationMode.Docked
+ : OperationMode.Handheld;
+
+ Context.ResponseData.Write((byte)Mode);
+
+ return 0;
+ }
+
+ public long GetPerformanceMode(ServiceCtx Context)
+ {
+ Apm.PerformanceMode Mode = Context.Device.System.State.DockedMode
+ ? Apm.PerformanceMode.Docked
+ : Apm.PerformanceMode.Handheld;
+
+ Context.ResponseData.Write((int)Mode);
+
+ return 0;
+ }
+
+ public long GetBootMode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((byte)0); //Unknown value.
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetCurrentFocusState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState);
+
+ return 0;
+ }
+
+ public long GetDefaultDisplayResolution(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1280);
+ Context.ResponseData.Write(720);
+
+ return 0;
+ }
+
+ public long GetDefaultDisplayResolutionChangeEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(DisplayResolutionChangeEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs
new file mode 100644
index 00000000..d86743c0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IDebugFunctions.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IDebugFunctions : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IDebugFunctions()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs b/Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs
new file mode 100644
index 00000000..c4d49579
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IDisplayController.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IDisplayController : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IDisplayController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs b/Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs
new file mode 100644
index 00000000..e646327f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IGlobalStateController.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IGlobalStateController : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IGlobalStateController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs
new file mode 100644
index 00000000..95028ca0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs
@@ -0,0 +1,46 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IHomeMenuFunctions : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent ChannelEvent;
+
+ public IHomeMenuFunctions()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 10, RequestToGetForeground },
+ { 21, GetPopFromGeneralChannelEvent }
+ };
+
+ //ToDo: Signal this Event somewhere in future.
+ ChannelEvent = new KEvent();
+ }
+
+ public long RequestToGetForeground(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetPopFromGeneralChannelEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(ChannelEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs
new file mode 100644
index 00000000..e099ec64
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs
@@ -0,0 +1,71 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class ILibraryAppletAccessor : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent StateChangedEvent;
+
+ public ILibraryAppletAccessor()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetAppletStateChangedEvent },
+ { 10, Start },
+ { 30, GetResult },
+ { 100, PushInData },
+ { 101, PopOutData }
+ };
+
+ StateChangedEvent = new KEvent();
+ }
+
+ public long GetAppletStateChangedEvent(ServiceCtx Context)
+ {
+ StateChangedEvent.WaitEvent.Set();
+
+ int Handle = Context.Process.HandleTable.OpenHandle(StateChangedEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long Start(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetResult(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long PushInData(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long PopOutData(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorage(StorageHelper.MakeLaunchParams()));
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs
new file mode 100644
index 00000000..065574c7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletCreator.cs
@@ -0,0 +1,37 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class ILibraryAppletCreator : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ILibraryAppletCreator()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateLibraryApplet },
+ { 10, CreateStorage }
+ };
+ }
+
+ public long CreateLibraryApplet(ServiceCtx Context)
+ {
+ MakeObject(Context, new ILibraryAppletAccessor());
+
+ return 0;
+ }
+
+ public long CreateStorage(ServiceCtx Context)
+ {
+ long Size = Context.RequestData.ReadInt64();
+
+ MakeObject(Context, new IStorage(new byte[Size]));
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs
new file mode 100644
index 00000000..ccd96e0d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs
@@ -0,0 +1,145 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class ISelfController : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent LaunchableEvent;
+
+ public ISelfController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Exit },
+ { 1, LockExit },
+ { 2, UnlockExit },
+ { 9, GetLibraryAppletLaunchableEvent },
+ { 10, SetScreenShotPermission },
+ { 11, SetOperationModeChangedNotification },
+ { 12, SetPerformanceModeChangedNotification },
+ { 13, SetFocusHandlingMode },
+ { 14, SetRestartMessageEnabled },
+ { 16, SetOutOfFocusSuspendingEnabled },
+ { 19, SetScreenShotImageOrientation },
+ { 50, SetHandlesRequestToDisplay }
+ };
+
+ LaunchableEvent = new KEvent();
+ }
+
+ public long Exit(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long LockExit(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long UnlockExit(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetLibraryAppletLaunchableEvent(ServiceCtx Context)
+ {
+ LaunchableEvent.WaitEvent.Set();
+
+ int Handle = Context.Process.HandleTable.OpenHandle(LaunchableEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetScreenShotPermission(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetOperationModeChangedNotification(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetPerformanceModeChangedNotification(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetFocusHandlingMode(ServiceCtx Context)
+ {
+ bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false;
+ bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false;
+ bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetRestartMessageEnabled(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetOutOfFocusSuspendingEnabled(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetScreenShotImageOrientation(ServiceCtx Context)
+ {
+ int Orientation = Context.RequestData.ReadInt32();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetHandlesRequestToDisplay(ServiceCtx Context)
+ {
+ bool Enable = Context.RequestData.ReadByte() != 0 ? true : false;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/IStorage.cs b/Ryujinx.HLE/HOS/Services/Am/IStorage.cs
new file mode 100644
index 00000000..10778122
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IStorage.cs
@@ -0,0 +1,31 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IStorage : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public byte[] Data { get; private set; }
+
+ public IStorage(byte[] Data)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Open }
+ };
+
+ this.Data = Data;
+ }
+
+ public long Open(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorageAccessor(this));
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs
new file mode 100644
index 00000000..a60cf149
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IStorageAccessor.cs
@@ -0,0 +1,83 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IStorageAccessor : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private IStorage Storage;
+
+ public IStorageAccessor(IStorage Storage)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetSize },
+ { 10, Write },
+ { 11, Read }
+ };
+
+ this.Storage = Storage;
+ }
+
+ public long GetSize(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)Storage.Data.Length);
+
+ return 0;
+ }
+
+ public long Write(ServiceCtx Context)
+ {
+ //TODO: Error conditions.
+ long WritePosition = Context.RequestData.ReadInt64();
+
+ (long Position, long Size) = Context.Request.GetBufferType0x21();
+
+ if (Size > 0)
+ {
+ long MaxSize = Storage.Data.Length - WritePosition;
+
+ if (Size > MaxSize)
+ {
+ Size = MaxSize;
+ }
+
+ byte[] Data = Context.Memory.ReadBytes(Position, Size);
+
+ Buffer.BlockCopy(Data, 0, Storage.Data, (int)WritePosition, (int)Size);
+ }
+
+ return 0;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ //TODO: Error conditions.
+ long ReadPosition = Context.RequestData.ReadInt64();
+
+ (long Position, long Size) = Context.Request.GetBufferType0x22();
+
+ byte[] Data;
+
+ if (Storage.Data.Length > Size)
+ {
+ Data = new byte[Size];
+
+ Buffer.BlockCopy(Storage.Data, 0, Data, 0, (int)Size);
+ }
+ else
+ {
+ Data = Storage.Data;
+ }
+
+ Context.Memory.WriteBytes(Position, Data);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs b/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs
new file mode 100644
index 00000000..c08d4018
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/ISystemAppletProxy.cs
@@ -0,0 +1,99 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class ISystemAppletProxy : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemAppletProxy()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCommonStateGetter },
+ { 1, GetSelfController },
+ { 2, GetWindowController },
+ { 3, GetAudioController },
+ { 4, GetDisplayController },
+ { 11, GetLibraryAppletCreator },
+ { 20, GetHomeMenuFunctions },
+ { 21, GetGlobalStateController },
+ { 22, GetApplicationCreator },
+ { 1000, GetDebugFunctions }
+ };
+ }
+
+ public long GetCommonStateGetter(ServiceCtx Context)
+ {
+ MakeObject(Context, new ICommonStateGetter());
+
+ return 0;
+ }
+
+ public long GetSelfController(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISelfController());
+
+ return 0;
+ }
+
+ public long GetWindowController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IWindowController());
+
+ return 0;
+ }
+
+ public long GetAudioController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAudioController());
+
+ return 0;
+ }
+
+ public long GetDisplayController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDisplayController());
+
+ return 0;
+ }
+
+ public long GetLibraryAppletCreator(ServiceCtx Context)
+ {
+ MakeObject(Context, new ILibraryAppletCreator());
+
+ return 0;
+ }
+
+ public long GetHomeMenuFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IHomeMenuFunctions());
+
+ return 0;
+ }
+
+ public long GetGlobalStateController(ServiceCtx Context)
+ {
+ MakeObject(Context, new IGlobalStateController());
+
+ return 0;
+ }
+
+ public long GetApplicationCreator(ServiceCtx Context)
+ {
+ MakeObject(Context, new IApplicationCreator());
+
+ return 0;
+ }
+
+ public long GetDebugFunctions(ServiceCtx Context)
+ {
+ MakeObject(Context, new IDebugFunctions());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs b/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs
new file mode 100644
index 00000000..1a5a716f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/IWindowController.cs
@@ -0,0 +1,38 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class IWindowController : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IWindowController()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, GetAppletResourceUserId },
+ { 10, AcquireForegroundRights }
+ };
+ }
+
+ public long GetAppletResourceUserId(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ Context.ResponseData.Write(0L);
+
+ return 0;
+ }
+
+ public long AcquireForegroundRights(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAm, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs b/Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs
new file mode 100644
index 00000000..65fddbed
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/MessageInfo.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ enum MessageInfo
+ {
+ FocusStateChanged = 0xf,
+ OperationModeChanged = 0x1e,
+ PerformanceModeChanged = 0x1f
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/OperationMode.cs b/Ryujinx.HLE/HOS/Services/Am/OperationMode.cs
new file mode 100644
index 00000000..cb11fff9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/OperationMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ enum OperationMode
+ {
+ Handheld = 0,
+ Docked = 1
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs b/Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs
new file mode 100644
index 00000000..b97ffc1e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/StorageHelper.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services.Am
+{
+ class StorageHelper
+ {
+ private const uint LaunchParamsMagic = 0xc79497ca;
+
+ public static byte[] MakeLaunchParams()
+ {
+ //Size needs to be at least 0x88 bytes otherwise application errors.
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ MS.SetLength(0x88);
+
+ Writer.Write(LaunchParamsMagic);
+ Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used.
+ Writer.Write(1L); //User Id Low (note: User Id needs to be != 0)
+ Writer.Write(0L); //User Id High
+
+ return MS.ToArray();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Apm/IManager.cs b/Ryujinx.HLE/HOS/Services/Apm/IManager.cs
new file mode 100644
index 00000000..50822def
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Apm/IManager.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Apm
+{
+ class IManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, OpenSession }
+ };
+ }
+
+ public long OpenSession(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISession());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Apm/ISession.cs b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs
new file mode 100644
index 00000000..739e264d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Apm/ISession.cs
@@ -0,0 +1,41 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Apm
+{
+ class ISession : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISession()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, SetPerformanceConfiguration },
+ { 1, GetPerformanceConfiguration }
+ };
+ }
+
+ public long SetPerformanceConfiguration(ServiceCtx Context)
+ {
+ PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32();
+ PerformanceConfiguration PerfConfig = (PerformanceConfiguration)Context.RequestData.ReadInt32();
+
+ return 0;
+ }
+
+ public long GetPerformanceConfiguration(ServiceCtx Context)
+ {
+ PerformanceMode PerfMode = (PerformanceMode)Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write((uint)PerformanceConfiguration.PerformanceConfiguration1);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceApm, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs b/Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs
new file mode 100644
index 00000000..b24adfb3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Apm/PerformanceConfiguration.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Services.Apm
+{
+ enum PerformanceConfiguration : uint
+ {
+ PerformanceConfiguration1 = 0x00010000,
+ PerformanceConfiguration2 = 0x00010001,
+ PerformanceConfiguration3 = 0x00010002,
+ PerformanceConfiguration4 = 0x00020000,
+ PerformanceConfiguration5 = 0x00020001,
+ PerformanceConfiguration6 = 0x00020002,
+ PerformanceConfiguration7 = 0x00020003,
+ PerformanceConfiguration8 = 0x00020004,
+ PerformanceConfiguration9 = 0x00020005,
+ PerformanceConfiguration10 = 0x00020006,
+ PerformanceConfiguration11 = 0x92220007,
+ PerformanceConfiguration12 = 0x92220008
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs b/Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs
new file mode 100644
index 00000000..c4ab3cf2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Apm/PerformanceMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Apm
+{
+ enum PerformanceMode
+ {
+ Handheld = 0,
+ Docked = 1
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs b/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs
new file mode 100644
index 00000000..cecea860
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudErr.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ static class AudErr
+ {
+ public const int DeviceNotFound = 1;
+ public const int UnsupportedRevision = 2;
+ public const int UnsupportedSampleRate = 3;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs
new file mode 100644
index 00000000..e25ebe66
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/AudioOutData.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct AudioOutData
+ {
+ public long NextBufferPtr;
+ public long SampleBufferPtr;
+ public long SampleBufferCapacity;
+ public long SampleBufferSize;
+ public long SampleBufferInnerOffset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs
new file mode 100644
index 00000000..81561f04
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs
@@ -0,0 +1,163 @@
+using ChocolArm64.Memory;
+using Ryujinx.Audio;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioOut
+{
+ class IAudioOut : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private IAalOutput AudioOut;
+
+ private KEvent ReleaseEvent;
+
+ private int Track;
+
+ public IAudioOut(IAalOutput AudioOut, KEvent ReleaseEvent, int Track)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetAudioOutState },
+ { 1, StartAudioOut },
+ { 2, StopAudioOut },
+ { 3, AppendAudioOutBuffer },
+ { 4, RegisterBufferEvent },
+ { 5, GetReleasedAudioOutBuffer },
+ { 6, ContainsAudioOutBuffer },
+ { 7, AppendAudioOutBufferAuto },
+ { 8, GetReleasedAudioOutBufferAuto }
+ };
+
+ this.AudioOut = AudioOut;
+ this.ReleaseEvent = ReleaseEvent;
+ this.Track = Track;
+ }
+
+ public long GetAudioOutState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)AudioOut.GetState(Track));
+
+ return 0;
+ }
+
+ public long StartAudioOut(ServiceCtx Context)
+ {
+ AudioOut.Start(Track);
+
+ return 0;
+ }
+
+ public long StopAudioOut(ServiceCtx Context)
+ {
+ AudioOut.Stop(Track);
+
+ return 0;
+ }
+
+ public long AppendAudioOutBuffer(ServiceCtx Context)
+ {
+ return AppendAudioOutBufferImpl(Context, Context.Request.SendBuff[0].Position);
+ }
+
+ public long RegisterBufferEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long GetReleasedAudioOutBuffer(ServiceCtx Context)
+ {
+ long Position = Context.Request.ReceiveBuff[0].Position;
+ long Size = Context.Request.ReceiveBuff[0].Size;
+
+ return GetReleasedAudioOutBufferImpl(Context, Position, Size);
+ }
+
+ public long ContainsAudioOutBuffer(ServiceCtx Context)
+ {
+ long Tag = Context.RequestData.ReadInt64();
+
+ Context.ResponseData.Write(AudioOut.ContainsBuffer(Track, Tag) ? 1 : 0);
+
+ return 0;
+ }
+
+ public long AppendAudioOutBufferAuto(ServiceCtx Context)
+ {
+ (long Position, long Size) = Context.Request.GetBufferType0x21();
+
+ return AppendAudioOutBufferImpl(Context, Position);
+ }
+
+ public long AppendAudioOutBufferImpl(ServiceCtx Context, long Position)
+ {
+ long Tag = Context.RequestData.ReadInt64();
+
+ AudioOutData Data = AMemoryHelper.Read<AudioOutData>(
+ Context.Memory,
+ Position);
+
+ byte[] Buffer = Context.Memory.ReadBytes(
+ Data.SampleBufferPtr,
+ Data.SampleBufferSize);
+
+ AudioOut.AppendBuffer(Track, Tag, Buffer);
+
+ return 0;
+ }
+
+ public long GetReleasedAudioOutBufferAuto(ServiceCtx Context)
+ {
+ (long Position, long Size) = Context.Request.GetBufferType0x22();
+
+ return GetReleasedAudioOutBufferImpl(Context, Position, Size);
+ }
+
+ public long GetReleasedAudioOutBufferImpl(ServiceCtx Context, long Position, long Size)
+ {
+ uint Count = (uint)((ulong)Size >> 3);
+
+ long[] ReleasedBuffers = AudioOut.GetReleasedBuffers(Track, (int)Count);
+
+ for (uint Index = 0; Index < Count; Index++)
+ {
+ long Tag = 0;
+
+ if (Index < ReleasedBuffers.Length)
+ {
+ Tag = ReleasedBuffers[Index];
+ }
+
+ Context.Memory.WriteInt64(Position + Index * 8, Tag);
+ }
+
+ Context.ResponseData.Write(ReleasedBuffers.Length);
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ AudioOut.CloseTrack(Track);
+
+ ReleaseEvent.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/AudioConsts.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/AudioConsts.cs
new file mode 100644
index 00000000..773ef6dd
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/AudioConsts.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ static class AudioConsts
+ {
+ public const int HostSampleRate = 48000;
+ public const int HostChannelsCount = 2;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs
new file mode 100644
index 00000000..69b6d522
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BehaviorIn.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct BehaviorIn
+ {
+ public long Unknown0;
+ public long Unknown8;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs
new file mode 100644
index 00000000..51dddf20
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/BiquadFilter.cs
@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)]
+ struct BiquadFilter
+ {
+ public byte Enable;
+ public byte Padding;
+ public short B0;
+ public short B1;
+ public short B2;
+ public short A1;
+ public short A2;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs
new file mode 100644
index 00000000..8c83338d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs
@@ -0,0 +1,318 @@
+using ChocolArm64.Memory;
+using Ryujinx.Audio;
+using Ryujinx.Audio.Adpcm;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ class IAudioRenderer : IpcService, IDisposable
+ {
+ //This is the amount of samples that are going to be appended
+ //each time that RequestUpdateAudioRenderer is called. Ideally,
+ //this value shouldn't be neither too small (to avoid the player
+ //starving due to running out of samples) or too large (to avoid
+ //high latency).
+ private const int MixBufferSamplesCount = 960;
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent UpdateEvent;
+
+ private AMemory Memory;
+
+ private IAalOutput AudioOut;
+
+ private AudioRendererParameter Params;
+
+ private MemoryPoolContext[] MemoryPools;
+
+ private VoiceContext[] Voices;
+
+ private int Track;
+
+ public IAudioRenderer(AMemory Memory, IAalOutput AudioOut, AudioRendererParameter Params)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, RequestUpdateAudioRenderer },
+ { 5, StartAudioRenderer },
+ { 6, StopAudioRenderer },
+ { 7, QuerySystemEvent }
+ };
+
+ UpdateEvent = new KEvent();
+
+ this.Memory = Memory;
+ this.AudioOut = AudioOut;
+ this.Params = Params;
+
+ Track = AudioOut.OpenTrack(
+ AudioConsts.HostSampleRate,
+ AudioConsts.HostChannelsCount,
+ AudioCallback);
+
+ MemoryPools = CreateArray<MemoryPoolContext>(Params.EffectCount + Params.VoiceCount * 4);
+
+ Voices = CreateArray<VoiceContext>(Params.VoiceCount);
+
+ InitializeAudioOut();
+ }
+
+ private void AudioCallback()
+ {
+ UpdateEvent.WaitEvent.Set();
+ }
+
+ private static T[] CreateArray<T>(int Size) where T : new()
+ {
+ T[] Output = new T[Size];
+
+ for (int Index = 0; Index < Size; Index++)
+ {
+ Output[Index] = new T();
+ }
+
+ return Output;
+ }
+
+ private void InitializeAudioOut()
+ {
+ AppendMixedBuffer(0);
+ AppendMixedBuffer(1);
+ AppendMixedBuffer(2);
+
+ AudioOut.Start(Track);
+ }
+
+ public long RequestUpdateAudioRenderer(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.ReceiveBuff[0].Position;
+ long OutputSize = Context.Request.ReceiveBuff[0].Size;
+
+ AMemoryHelper.FillWithZeros(Context.Memory, OutputPosition, (int)OutputSize);
+
+ long InputPosition = Context.Request.SendBuff[0].Position;
+
+ StructReader Reader = new StructReader(Context.Memory, InputPosition);
+ StructWriter Writer = new StructWriter(Context.Memory, OutputPosition);
+
+ UpdateDataHeader InputHeader = Reader.Read<UpdateDataHeader>();
+
+ Reader.Read<BehaviorIn>(InputHeader.BehaviorSize);
+
+ MemoryPoolIn[] MemoryPoolsIn = Reader.Read<MemoryPoolIn>(InputHeader.MemoryPoolSize);
+
+ for (int Index = 0; Index < MemoryPoolsIn.Length; Index++)
+ {
+ MemoryPoolIn MemoryPool = MemoryPoolsIn[Index];
+
+ if (MemoryPool.State == MemoryPoolState.RequestAttach)
+ {
+ MemoryPools[Index].OutStatus.State = MemoryPoolState.Attached;
+ }
+ else if (MemoryPool.State == MemoryPoolState.RequestDetach)
+ {
+ MemoryPools[Index].OutStatus.State = MemoryPoolState.Detached;
+ }
+ }
+
+ Reader.Read<VoiceChannelResourceIn>(InputHeader.VoiceResourceSize);
+
+ VoiceIn[] VoicesIn = Reader.Read<VoiceIn>(InputHeader.VoiceSize);
+
+ for (int Index = 0; Index < VoicesIn.Length; Index++)
+ {
+ VoiceIn Voice = VoicesIn[Index];
+
+ VoiceContext VoiceCtx = Voices[Index];
+
+ VoiceCtx.SetAcquireState(Voice.Acquired != 0);
+
+ if (Voice.Acquired == 0)
+ {
+ continue;
+ }
+
+ if (Voice.FirstUpdate != 0)
+ {
+ VoiceCtx.AdpcmCtx = GetAdpcmDecoderContext(
+ Voice.AdpcmCoeffsPosition,
+ Voice.AdpcmCoeffsSize);
+
+ VoiceCtx.SampleFormat = Voice.SampleFormat;
+ VoiceCtx.SampleRate = Voice.SampleRate;
+ VoiceCtx.ChannelsCount = Voice.ChannelsCount;
+
+ VoiceCtx.SetBufferIndex(Voice.BaseWaveBufferIndex);
+ }
+
+ VoiceCtx.WaveBuffers[0] = Voice.WaveBuffer0;
+ VoiceCtx.WaveBuffers[1] = Voice.WaveBuffer1;
+ VoiceCtx.WaveBuffers[2] = Voice.WaveBuffer2;
+ VoiceCtx.WaveBuffers[3] = Voice.WaveBuffer3;
+ VoiceCtx.Volume = Voice.Volume;
+ VoiceCtx.PlayState = Voice.PlayState;
+ }
+
+ UpdateAudio();
+
+ UpdateDataHeader OutputHeader = new UpdateDataHeader();
+
+ int UpdateHeaderSize = Marshal.SizeOf<UpdateDataHeader>();
+
+ OutputHeader.Revision = IAudioRendererManager.RevMagic;
+ OutputHeader.BehaviorSize = 0xb0;
+ OutputHeader.MemoryPoolSize = (Params.EffectCount + Params.VoiceCount * 4) * 0x10;
+ OutputHeader.VoiceSize = Params.VoiceCount * 0x10;
+ OutputHeader.EffectSize = Params.EffectCount * 0x10;
+ OutputHeader.SinkSize = Params.SinkCount * 0x20;
+ OutputHeader.PerformanceManagerSize = 0x10;
+ OutputHeader.TotalSize = UpdateHeaderSize +
+ OutputHeader.BehaviorSize +
+ OutputHeader.MemoryPoolSize +
+ OutputHeader.VoiceSize +
+ OutputHeader.EffectSize +
+ OutputHeader.SinkSize +
+ OutputHeader.PerformanceManagerSize;
+
+ Writer.Write(OutputHeader);
+
+ foreach (MemoryPoolContext MemoryPool in MemoryPools)
+ {
+ Writer.Write(MemoryPool.OutStatus);
+ }
+
+ foreach (VoiceContext Voice in Voices)
+ {
+ Writer.Write(Voice.OutStatus);
+ }
+
+ return 0;
+ }
+
+ public long StartAudioRenderer(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long StopAudioRenderer(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long QuerySystemEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(UpdateEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ private AdpcmDecoderContext GetAdpcmDecoderContext(long Position, long Size)
+ {
+ if (Size == 0)
+ {
+ return null;
+ }
+
+ AdpcmDecoderContext Context = new AdpcmDecoderContext();
+
+ Context.Coefficients = new short[Size >> 1];
+
+ for (int Offset = 0; Offset < Size; Offset += 2)
+ {
+ Context.Coefficients[Offset >> 1] = Memory.ReadInt16(Position + Offset);
+ }
+
+ return Context;
+ }
+
+ private void UpdateAudio()
+ {
+ long[] Released = AudioOut.GetReleasedBuffers(Track, 2);
+
+ for (int Index = 0; Index < Released.Length; Index++)
+ {
+ AppendMixedBuffer(Released[Index]);
+ }
+ }
+
+ private void AppendMixedBuffer(long Tag)
+ {
+ int[] MixBuffer = new int[MixBufferSamplesCount * AudioConsts.HostChannelsCount];
+
+ foreach (VoiceContext Voice in Voices)
+ {
+ if (!Voice.Playing)
+ {
+ continue;
+ }
+
+ int OutOffset = 0;
+
+ int PendingSamples = MixBufferSamplesCount;
+
+ while (PendingSamples > 0)
+ {
+ int[] Samples = Voice.GetBufferData(Memory, PendingSamples, out int ReturnedSamples);
+
+ if (ReturnedSamples == 0)
+ {
+ break;
+ }
+
+ PendingSamples -= ReturnedSamples;
+
+ for (int Offset = 0; Offset < Samples.Length; Offset++)
+ {
+ int Sample = (int)(Samples[Offset] * Voice.Volume);
+
+ MixBuffer[OutOffset++] += Sample;
+ }
+ }
+ }
+
+ AudioOut.AppendBuffer(Track, Tag, GetFinalBuffer(MixBuffer));
+ }
+
+ private static short[] GetFinalBuffer(int[] Buffer)
+ {
+ short[] Output = new short[Buffer.Length];
+
+ for (int Offset = 0; Offset < Buffer.Length; Offset++)
+ {
+ Output[Offset] = DspUtils.Saturate(Buffer[Offset]);
+ }
+
+ return Output;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ AudioOut.CloseTrack(Track);
+
+ UpdateEvent.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolContext.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolContext.cs
new file mode 100644
index 00000000..2b68c40d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolContext.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ class MemoryPoolContext
+ {
+ public MemoryPoolOut OutStatus;
+
+ public MemoryPoolContext()
+ {
+ OutStatus.State = MemoryPoolState.Detached;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs
new file mode 100644
index 00000000..f6e424ee
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolIn.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)]
+ struct MemoryPoolIn
+ {
+ public long Address;
+ public long Size;
+ public MemoryPoolState State;
+ public int Unknown14;
+ public long Unknown18;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs
new file mode 100644
index 00000000..f0621e3e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolOut.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct MemoryPoolOut
+ {
+ public MemoryPoolState State;
+ public int Unknown14;
+ public long Unknown18;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolState.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolState.cs
new file mode 100644
index 00000000..6baf507c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/MemoryPoolState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ enum MemoryPoolState : int
+ {
+ Invalid = 0,
+ Unknown = 1,
+ RequestDetach = 2,
+ Detached = 3,
+ RequestAttach = 4,
+ Attached = 5,
+ Released = 6
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs
new file mode 100644
index 00000000..f09bf46e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/PlayState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ enum PlayState : byte
+ {
+ Playing = 0,
+ Stopped = 1,
+ Paused = 2
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs
new file mode 100644
index 00000000..baa0bc62
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/Resampler.cs
@@ -0,0 +1,191 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ static class Resampler
+ {
+#region "LookUp Tables"
+ private static short[] CurveLut0 = new short[]
+ {
+ 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
+ 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
+ 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
+ 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
+ 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
+ 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
+ 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
+ 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
+ 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
+ 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
+ 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
+ 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
+ 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
+ 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
+ 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
+ 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
+ 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
+ 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
+ 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
+ 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
+ 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
+ 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
+ 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
+ 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
+ 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
+ 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
+ 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
+ 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
+ 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
+ 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
+ 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
+ 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
+ };
+
+ private static short[] CurveLut1 = new short[]
+ {
+ -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
+ -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
+ -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
+ -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
+ -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
+ -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
+ -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
+ -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
+ -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
+ -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
+ -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
+ -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
+ -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
+ -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
+ -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
+ -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
+ -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
+ -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
+ -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
+ -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
+ -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
+ -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
+ -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
+ -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
+ -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
+ -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
+ -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
+ -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
+ -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
+ -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
+ -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
+ -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
+ };
+
+ private static short[] CurveLut2 = new short[]
+ {
+ 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
+ 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
+ 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
+ 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
+ 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
+ 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
+ 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
+ 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
+ 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
+ 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
+ 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
+ -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
+ -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
+ -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
+ -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
+ -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
+ -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
+ -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
+ -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
+ -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
+ -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
+ -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
+ -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
+ -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
+ -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
+ -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
+ -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
+ -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
+ -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
+ -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
+ -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
+ -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
+ };
+#endregion
+
+ public static int[] Resample2Ch(
+ int[] Buffer,
+ int SrcSampleRate,
+ int DstSampleRate,
+ int SamplesCount,
+ ref int FracPart)
+ {
+ if (Buffer == null)
+ {
+ throw new ArgumentNullException(nameof(Buffer));
+ }
+
+ if (SrcSampleRate <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(SrcSampleRate));
+ }
+
+ if (DstSampleRate <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(DstSampleRate));
+ }
+
+ double Ratio = (double)SrcSampleRate / DstSampleRate;
+
+ int NewSamplesCount = (int)(SamplesCount / Ratio);
+
+ int Step = (int)(Ratio * 0x8000);
+
+ int[] Output = new int[NewSamplesCount * 2];
+
+ short[] Lut;
+
+ if (Step > 0xaaaa)
+ {
+ Lut = CurveLut0;
+ }
+ else if (Step <= 0x8000)
+ {
+ Lut = CurveLut1;
+ }
+ else
+ {
+ Lut = CurveLut2;
+ }
+
+ int InOffs = 0;
+
+ for (int OutOffs = 0; OutOffs < Output.Length; OutOffs += 2)
+ {
+ int LutIndex = (FracPart >> 8) * 4;
+
+ int Sample0 = Buffer[(InOffs + 0) * 2 + 0] * Lut[LutIndex + 0] +
+ Buffer[(InOffs + 1) * 2 + 0] * Lut[LutIndex + 1] +
+ Buffer[(InOffs + 2) * 2 + 0] * Lut[LutIndex + 2] +
+ Buffer[(InOffs + 3) * 2 + 0] * Lut[LutIndex + 3];
+
+ int Sample1 = Buffer[(InOffs + 0) * 2 + 1] * Lut[LutIndex + 0] +
+ Buffer[(InOffs + 1) * 2 + 1] * Lut[LutIndex + 1] +
+ Buffer[(InOffs + 2) * 2 + 1] * Lut[LutIndex + 2] +
+ Buffer[(InOffs + 3) * 2 + 1] * Lut[LutIndex + 3];
+
+ int NewOffset = FracPart + Step;
+
+ InOffs += NewOffset >> 15;
+
+ FracPart = NewOffset & 0x7fff;
+
+ Output[OutOffs + 0] = Sample0 >> 15;
+ Output[OutOffs + 1] = Sample1 >> 15;
+ }
+
+ return Output;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/UpdateDataHeader.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/UpdateDataHeader.cs
new file mode 100644
index 00000000..a71e865d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/UpdateDataHeader.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ struct UpdateDataHeader
+ {
+ public int Revision;
+ public int BehaviorSize;
+ public int MemoryPoolSize;
+ public int VoiceSize;
+ public int VoiceResourceSize;
+ public int EffectSize;
+ public int MixeSize;
+ public int SinkSize;
+ public int PerformanceManagerSize;
+ public int Unknown24;
+ public int Unknown28;
+ public int Unknown2C;
+ public int Unknown30;
+ public int Unknown34;
+ public int Unknown38;
+ public int TotalSize;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceChannelResourceIn.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceChannelResourceIn.cs
new file mode 100644
index 00000000..2a6f424f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceChannelResourceIn.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
+ struct VoiceChannelResourceIn
+ {
+ //???
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs
new file mode 100644
index 00000000..bee574ff
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceContext.cs
@@ -0,0 +1,188 @@
+using ChocolArm64.Memory;
+using Ryujinx.Audio.Adpcm;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ class VoiceContext
+ {
+ private bool Acquired;
+ private bool BufferReload;
+
+ private int ResamplerFracPart;
+
+ private int BufferIndex;
+ private int Offset;
+
+ public int SampleRate;
+ public int ChannelsCount;
+
+ public float Volume;
+
+ public PlayState PlayState;
+
+ public SampleFormat SampleFormat;
+
+ public AdpcmDecoderContext AdpcmCtx;
+
+ public WaveBuffer[] WaveBuffers;
+
+ public VoiceOut OutStatus;
+
+ private int[] Samples;
+
+ public bool Playing => Acquired && PlayState == PlayState.Playing;
+
+ public VoiceContext()
+ {
+ WaveBuffers = new WaveBuffer[4];
+ }
+
+ public void SetAcquireState(bool NewState)
+ {
+ if (Acquired && !NewState)
+ {
+ //Release.
+ Reset();
+ }
+
+ Acquired = NewState;
+ }
+
+ private void Reset()
+ {
+ BufferReload = true;
+
+ BufferIndex = 0;
+ Offset = 0;
+
+ OutStatus.PlayedSamplesCount = 0;
+ OutStatus.PlayedWaveBuffersCount = 0;
+ OutStatus.VoiceDropsCount = 0;
+ }
+
+ public int[] GetBufferData(AMemory Memory, int MaxSamples, out int SamplesCount)
+ {
+ if (!Playing)
+ {
+ SamplesCount = 0;
+
+ return null;
+ }
+
+ if (BufferReload)
+ {
+ BufferReload = false;
+
+ UpdateBuffer(Memory);
+ }
+
+ WaveBuffer Wb = WaveBuffers[BufferIndex];
+
+ int MaxSize = Samples.Length - Offset;
+
+ int Size = MaxSamples * AudioConsts.HostChannelsCount;
+
+ if (Size > MaxSize)
+ {
+ Size = MaxSize;
+ }
+
+ int[] Output = new int[Size];
+
+ Array.Copy(Samples, Offset, Output, 0, Size);
+
+ SamplesCount = Size / AudioConsts.HostChannelsCount;
+
+ OutStatus.PlayedSamplesCount += SamplesCount;
+
+ Offset += Size;
+
+ if (Offset == Samples.Length)
+ {
+ Offset = 0;
+
+ if (Wb.Looping == 0)
+ {
+ SetBufferIndex((BufferIndex + 1) & 3);
+ }
+
+ OutStatus.PlayedWaveBuffersCount++;
+
+ if (Wb.LastBuffer != 0)
+ {
+ PlayState = PlayState.Paused;
+ }
+ }
+
+ return Output;
+ }
+
+ private void UpdateBuffer(AMemory Memory)
+ {
+ //TODO: Implement conversion for formats other
+ //than interleaved stereo (2 channels).
+ //As of now, it assumes that HostChannelsCount == 2.
+ WaveBuffer Wb = WaveBuffers[BufferIndex];
+
+ if (SampleFormat == SampleFormat.PcmInt16)
+ {
+ int SamplesCount = (int)(Wb.Size / (sizeof(short) * ChannelsCount));
+
+ Samples = new int[SamplesCount * AudioConsts.HostChannelsCount];
+
+ if (ChannelsCount == 1)
+ {
+ for (int Index = 0; Index < SamplesCount; Index++)
+ {
+ short Sample = Memory.ReadInt16(Wb.Position + Index * 2);
+
+ Samples[Index * 2 + 0] = Sample;
+ Samples[Index * 2 + 1] = Sample;
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < SamplesCount * 2; Index++)
+ {
+ Samples[Index] = Memory.ReadInt16(Wb.Position + Index * 2);
+ }
+ }
+ }
+ else if (SampleFormat == SampleFormat.Adpcm)
+ {
+ byte[] Buffer = Memory.ReadBytes(Wb.Position, Wb.Size);
+
+ Samples = AdpcmDecoder.Decode(Buffer, AdpcmCtx);
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (SampleRate != AudioConsts.HostSampleRate)
+ {
+ //TODO: We should keep the frames being discarded (see the 4 below)
+ //on a buffer and include it on the next samples buffer, to allow
+ //the resampler to do seamless interpolation between wave buffers.
+ int SamplesCount = Samples.Length / AudioConsts.HostChannelsCount;
+
+ SamplesCount = Math.Max(SamplesCount - 4, 0);
+
+ Samples = Resampler.Resample2Ch(
+ Samples,
+ SampleRate,
+ AudioConsts.HostSampleRate,
+ SamplesCount,
+ ref ResamplerFracPart);
+ }
+ }
+
+ public void SetBufferIndex(int Index)
+ {
+ BufferIndex = Index & 3;
+
+ BufferReload = true;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs
new file mode 100644
index 00000000..fc83b65b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceIn.cs
@@ -0,0 +1,49 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
+ struct VoiceIn
+ {
+ public int VoiceSlot;
+ public int NodeId;
+
+ public byte FirstUpdate;
+ public byte Acquired;
+
+ public PlayState PlayState;
+
+ public SampleFormat SampleFormat;
+
+ public int SampleRate;
+
+ public int Priority;
+
+ public int Unknown14;
+
+ public int ChannelsCount;
+
+ public float Pitch;
+ public float Volume;
+
+ public BiquadFilter BiquadFilter0;
+ public BiquadFilter BiquadFilter1;
+
+ public int AppendedWaveBuffersCount;
+
+ public int BaseWaveBufferIndex;
+
+ public int Unknown44;
+
+ public long AdpcmCoeffsPosition;
+ public long AdpcmCoeffsSize;
+
+ public int VoiceDestination;
+ public int Padding;
+
+ public WaveBuffer WaveBuffer0;
+ public WaveBuffer WaveBuffer1;
+ public WaveBuffer WaveBuffer2;
+ public WaveBuffer WaveBuffer3;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs
new file mode 100644
index 00000000..6551acdb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/VoiceOut.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+ struct VoiceOut
+ {
+ public long PlayedSamplesCount;
+ public int PlayedWaveBuffersCount;
+ public int VoiceDropsCount; //?
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs
new file mode 100644
index 00000000..bb188877
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/WaveBuffer.cs
@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
+ struct WaveBuffer
+ {
+ public long Position;
+ public long Size;
+ public int FirstSampleOffset;
+ public int LastSampleOffset;
+ public byte Looping;
+ public byte LastBuffer;
+ public short Unknown1A;
+ public int Unknown1C;
+ public long AdpcmLoopContextPosition;
+ public long AdpcmLoopContextSize;
+ public long Unknown30;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs
new file mode 100644
index 00000000..4d01a0de
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRendererParameter.cs
@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct AudioRendererParameter
+ {
+ public int SampleRate;
+ public int SampleCount;
+ public int Unknown8;
+ public int MixCount;
+ public int VoiceCount;
+ public int SinkCount;
+ public int EffectCount;
+ public int PerformanceManagerCount;
+ public int VoiceDropEnable;
+ public int SplitterCount;
+ public int SplitterDestinationDataCount;
+ public int Unknown2C;
+ public int Revision;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs
new file mode 100644
index 00000000..2e6056ef
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs
@@ -0,0 +1,223 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ class IAudioDevice : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent SystemEvent;
+
+ public IAudioDevice()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, ListAudioDeviceName },
+ { 1, SetAudioDeviceOutputVolume },
+ { 3, GetActiveAudioDeviceName },
+ { 4, QueryAudioDeviceSystemEvent },
+ { 5, GetActiveChannelCount },
+ { 6, ListAudioDeviceNameAuto },
+ { 7, SetAudioDeviceOutputVolumeAuto },
+ { 8, GetAudioDeviceOutputVolumeAuto },
+ { 10, GetActiveAudioDeviceNameAuto },
+ { 11, QueryAudioDeviceInputEvent },
+ { 12, QueryAudioDeviceOutputEvent }
+ };
+
+ SystemEvent = new KEvent();
+
+ //TODO: We shouldn't be signaling this here.
+ SystemEvent.WaitEvent.Set();
+ }
+
+ public long ListAudioDeviceName(ServiceCtx Context)
+ {
+ string[] DeviceNames = SystemStateMgr.AudioOutputs;
+
+ Context.ResponseData.Write(DeviceNames.Length);
+
+ long Position = Context.Request.ReceiveBuff[0].Position;
+ long Size = Context.Request.ReceiveBuff[0].Size;
+
+ long BasePosition = Position;
+
+ foreach (string Name in DeviceNames)
+ {
+ byte[] Buffer = Encoding.ASCII.GetBytes(Name + "\0");
+
+ if ((Position - BasePosition) + Buffer.Length > Size)
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
+
+ break;
+ }
+
+ Context.Memory.WriteBytes(Position, Buffer);
+
+ Position += Buffer.Length;
+ }
+
+ return 0;
+ }
+
+ public long SetAudioDeviceOutputVolume(ServiceCtx Context)
+ {
+ float Volume = Context.RequestData.ReadSingle();
+
+ long Position = Context.Request.SendBuff[0].Position;
+ long Size = Context.Request.SendBuff[0].Size;
+
+ byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size);
+
+ string DeviceName = Encoding.ASCII.GetString(DeviceNameBuffer);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetActiveAudioDeviceName(ServiceCtx Context)
+ {
+ string Name = Context.Device.System.State.ActiveAudioOutput;
+
+ long Position = Context.Request.ReceiveBuff[0].Position;
+ long Size = Context.Request.ReceiveBuff[0].Size;
+
+ byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(Name + "\0");
+
+ if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
+ {
+ Context.Memory.WriteBytes(Position, DeviceNameBuffer);
+ }
+ else
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
+ }
+
+ return 0;
+ }
+
+ public long QueryAudioDeviceSystemEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetActiveChannelCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(2);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ListAudioDeviceNameAuto(ServiceCtx Context)
+ {
+ string[] DeviceNames = SystemStateMgr.AudioOutputs;
+
+ Context.ResponseData.Write(DeviceNames.Length);
+
+ (long Position, long Size) = Context.Request.GetBufferType0x22();
+
+ long BasePosition = Position;
+
+ foreach (string Name in DeviceNames)
+ {
+ byte[] Buffer = Encoding.UTF8.GetBytes(Name + '\0');
+
+ if ((Position - BasePosition) + Buffer.Length > Size)
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
+
+ break;
+ }
+
+ Context.Memory.WriteBytes(Position, Buffer);
+
+ Position += Buffer.Length;
+ }
+
+ return 0;
+ }
+
+ public long SetAudioDeviceOutputVolumeAuto(ServiceCtx Context)
+ {
+ float Volume = Context.RequestData.ReadSingle();
+
+ (long Position, long Size) = Context.Request.GetBufferType0x21();
+
+ byte[] DeviceNameBuffer = Context.Memory.ReadBytes(Position, Size);
+
+ string DeviceName = Encoding.UTF8.GetString(DeviceNameBuffer);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetAudioDeviceOutputVolumeAuto(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1f);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetActiveAudioDeviceNameAuto(ServiceCtx Context)
+ {
+ string Name = Context.Device.System.State.ActiveAudioOutput;
+
+ (long Position, long Size) = Context.Request.GetBufferType0x22();
+
+ byte[] DeviceNameBuffer = Encoding.UTF8.GetBytes(Name + '\0');
+
+ if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
+ {
+ Context.Memory.WriteBytes(Position, DeviceNameBuffer);
+ }
+ else
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
+ }
+
+ return 0;
+ }
+
+ public long QueryAudioDeviceInputEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+
+ public long QueryAudioDeviceOutputEvent(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(SystemEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceAudio, "Stubbed.");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs
new file mode 100644
index 00000000..8d2435b0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs
@@ -0,0 +1,170 @@
+using ChocolArm64.Memory;
+using Ryujinx.Audio;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Aud.AudioOut;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+using System.Text;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ class IAudioOutManager : IpcService
+ {
+ private const string DefaultAudioOutput = "DeviceOut";
+
+ private const int DefaultSampleRate = 48000;
+
+ private const int DefaultChannelsCount = 2;
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioOutManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, ListAudioOuts },
+ { 1, OpenAudioOut },
+ { 2, ListAudioOutsAuto },
+ { 3, OpenAudioOutAuto }
+ };
+ }
+
+ public long ListAudioOuts(ServiceCtx Context)
+ {
+ return ListAudioOutsImpl(
+ Context,
+ Context.Request.ReceiveBuff[0].Position,
+ Context.Request.ReceiveBuff[0].Size);
+ }
+
+ public long OpenAudioOut(ServiceCtx Context)
+ {
+ return OpenAudioOutImpl(
+ Context,
+ Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size,
+ Context.Request.ReceiveBuff[0].Position,
+ Context.Request.ReceiveBuff[0].Size);
+ }
+
+ public long ListAudioOutsAuto(ServiceCtx Context)
+ {
+ (long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22();
+
+ return ListAudioOutsImpl(Context, RecvPosition, RecvSize);
+ }
+
+ public long OpenAudioOutAuto(ServiceCtx Context)
+ {
+ (long SendPosition, long SendSize) = Context.Request.GetBufferType0x21();
+ (long RecvPosition, long RecvSize) = Context.Request.GetBufferType0x22();
+
+ return OpenAudioOutImpl(
+ Context,
+ SendPosition,
+ SendSize,
+ RecvPosition,
+ RecvSize);
+ }
+
+ private long ListAudioOutsImpl(ServiceCtx Context, long Position, long Size)
+ {
+ int NameCount = 0;
+
+ byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0");
+
+ if ((ulong)DeviceNameBuffer.Length <= (ulong)Size)
+ {
+ Context.Memory.WriteBytes(Position, DeviceNameBuffer);
+
+ NameCount++;
+ }
+ else
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {Size} too small!");
+ }
+
+ Context.ResponseData.Write(NameCount);
+
+ return 0;
+ }
+
+ private long OpenAudioOutImpl(ServiceCtx Context, long SendPosition, long SendSize, long ReceivePosition, long ReceiveSize)
+ {
+ string DeviceName = AMemoryHelper.ReadAsciiString(
+ Context.Memory,
+ SendPosition,
+ SendSize);
+
+ if (DeviceName == string.Empty)
+ {
+ DeviceName = DefaultAudioOutput;
+ }
+
+ if (DeviceName != DefaultAudioOutput)
+ {
+ Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid device name!");
+
+ return MakeError(ErrorModule.Audio, AudErr.DeviceNotFound);
+ }
+
+ byte[] DeviceNameBuffer = Encoding.ASCII.GetBytes(DeviceName + "\0");
+
+ if ((ulong)DeviceNameBuffer.Length <= (ulong)ReceiveSize)
+ {
+ Context.Memory.WriteBytes(ReceivePosition, DeviceNameBuffer);
+ }
+ else
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceAudio, $"Output buffer size {ReceiveSize} too small!");
+ }
+
+ int SampleRate = Context.RequestData.ReadInt32();
+ int Channels = Context.RequestData.ReadInt32();
+
+ if (SampleRate == 0)
+ {
+ SampleRate = DefaultSampleRate;
+ }
+
+ if (SampleRate != DefaultSampleRate)
+ {
+ Context.Device.Log.PrintWarning(LogClass.Audio, "Invalid sample rate!");
+
+ return MakeError(ErrorModule.Audio, AudErr.UnsupportedSampleRate);
+ }
+
+ Channels = (ushort)Channels;
+
+ if (Channels == 0)
+ {
+ Channels = DefaultChannelsCount;
+ }
+
+ KEvent ReleaseEvent = new KEvent();
+
+ ReleaseCallback Callback = () =>
+ {
+ ReleaseEvent.WaitEvent.Set();
+ };
+
+ IAalOutput AudioOut = Context.Device.AudioOut;
+
+ int Track = AudioOut.OpenTrack(SampleRate, Channels, Callback);
+
+ MakeObject(Context, new IAudioOut(AudioOut, ReleaseEvent, Track));
+
+ Context.ResponseData.Write(SampleRate);
+ Context.ResponseData.Write(Channels);
+ Context.ResponseData.Write((int)SampleFormat.PcmInt16);
+ Context.ResponseData.Write((int)PlaybackState.Stopped);
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs
new file mode 100644
index 00000000..faa42290
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioRendererManager.cs
@@ -0,0 +1,169 @@
+using Ryujinx.Audio;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Services.Aud.AudioRenderer;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ class IAudioRendererManager : IpcService
+ {
+ private const int Rev0Magic = ('R' << 0) |
+ ('E' << 8) |
+ ('V' << 16) |
+ ('0' << 24);
+
+ private const int Rev = 4;
+
+ public const int RevMagic = Rev0Magic + (Rev << 24);
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAudioRendererManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, OpenAudioRenderer },
+ { 1, GetAudioRendererWorkBufferSize },
+ { 2, GetAudioDevice }
+ };
+ }
+
+ public long OpenAudioRenderer(ServiceCtx Context)
+ {
+ IAalOutput AudioOut = Context.Device.AudioOut;
+
+ AudioRendererParameter Params = GetAudioRendererParameter(Context);
+
+ MakeObject(Context, new IAudioRenderer(Context.Memory, AudioOut, Params));
+
+ return 0;
+ }
+
+ public long GetAudioRendererWorkBufferSize(ServiceCtx Context)
+ {
+ AudioRendererParameter Params = GetAudioRendererParameter(Context);
+
+ int Revision = (Params.Revision - Rev0Magic) >> 24;
+
+ if (Revision <= Rev)
+ {
+ bool IsSplitterSupported = Revision >= 3;
+
+ long Size;
+
+ Size = IntUtils.AlignUp(Params.Unknown8 * 4, 64);
+ Size += Params.MixCount * 0x400;
+ Size += (Params.MixCount + 1) * 0x940;
+ Size += Params.VoiceCount * 0x3F0;
+ Size += IntUtils.AlignUp((Params.MixCount + 1) * 8, 16);
+ Size += IntUtils.AlignUp(Params.VoiceCount * 8, 16);
+ Size += IntUtils.AlignUp(
+ ((Params.SinkCount + Params.MixCount) * 0x3C0 + Params.SampleCount * 4) *
+ (Params.Unknown8 + 6), 64);
+ Size += (Params.SinkCount + Params.MixCount) * 0x2C0;
+ Size += (Params.EffectCount + Params.VoiceCount * 4) * 0x30 + 0x50;
+
+ if (IsSplitterSupported)
+ {
+ Size += IntUtils.AlignUp((
+ NodeStatesGetWorkBufferSize(Params.MixCount + 1) +
+ EdgeMatrixGetWorkBufferSize(Params.MixCount + 1)), 16);
+
+ Size += Params.SplitterDestinationDataCount * 0xE0;
+ Size += Params.SplitterCount * 0x20;
+ Size += IntUtils.AlignUp(Params.SplitterDestinationDataCount * 4, 16);
+ }
+
+ Size = Params.EffectCount * 0x4C0 +
+ Params.SinkCount * 0x170 +
+ Params.VoiceCount * 0x100 +
+ IntUtils.AlignUp(Size, 64) + 0x40;
+
+ if (Params.PerformanceManagerCount >= 1)
+ {
+ Size += (((Params.EffectCount +
+ Params.SinkCount +
+ Params.VoiceCount +
+ Params.MixCount + 1) * 16 + 0x658) *
+ (Params.PerformanceManagerCount + 1) + 0x13F) & ~0x3FL;
+ }
+
+ Size = (Size + 0x1907D) & ~0xFFFL;
+
+ Context.ResponseData.Write(Size);
+
+ Context.Device.Log.PrintDebug(LogClass.ServiceAudio, $"WorkBufferSize is 0x{Size:x16}.");
+
+ return 0;
+ }
+ else
+ {
+ Context.ResponseData.Write(0L);
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceAudio, $"Library Revision 0x{Params.Revision:x8} is not supported!");
+
+ return MakeError(ErrorModule.Audio, AudErr.UnsupportedRevision);
+ }
+ }
+
+ private AudioRendererParameter GetAudioRendererParameter(ServiceCtx Context)
+ {
+ AudioRendererParameter Params = new AudioRendererParameter();
+
+ Params.SampleRate = Context.RequestData.ReadInt32();
+ Params.SampleCount = Context.RequestData.ReadInt32();
+ Params.Unknown8 = Context.RequestData.ReadInt32();
+ Params.MixCount = Context.RequestData.ReadInt32();
+ Params.VoiceCount = Context.RequestData.ReadInt32();
+ Params.SinkCount = Context.RequestData.ReadInt32();
+ Params.EffectCount = Context.RequestData.ReadInt32();
+ Params.PerformanceManagerCount = Context.RequestData.ReadInt32();
+ Params.VoiceDropEnable = Context.RequestData.ReadInt32();
+ Params.SplitterCount = Context.RequestData.ReadInt32();
+ Params.SplitterDestinationDataCount = Context.RequestData.ReadInt32();
+ Params.Unknown2C = Context.RequestData.ReadInt32();
+ Params.Revision = Context.RequestData.ReadInt32();
+
+ return Params;
+ }
+
+ private static int NodeStatesGetWorkBufferSize(int Value)
+ {
+ int Result = IntUtils.AlignUp(Value, 64);
+
+ if (Result < 0)
+ {
+ Result |= 7;
+ }
+
+ return 4 * (Value * Value) + 0x12 * Value + 2 * (Result / 8);
+ }
+
+ private static int EdgeMatrixGetWorkBufferSize(int Value)
+ {
+ int Result = IntUtils.AlignUp(Value * Value, 64);
+
+ if (Result < 0)
+ {
+ Result |= 7;
+ }
+
+ return Result / 8;
+ }
+
+ public long GetAudioDevice(ServiceCtx Context)
+ {
+ long UserId = Context.RequestData.ReadInt64();
+
+ MakeObject(Context, new IAudioDevice());
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Aud/SampleFormat.cs b/Ryujinx.HLE/HOS/Services/Aud/SampleFormat.cs
new file mode 100644
index 00000000..8da80381
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Aud/SampleFormat.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Aud
+{
+ enum SampleFormat : byte
+ {
+ Invalid = 0,
+ PcmInt8 = 1,
+ PcmInt16 = 2,
+ PcmInt24 = 3,
+ PcmInt32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IBcatService.cs b/Ryujinx.HLE/HOS/Services/Bcat/IBcatService.cs
new file mode 100644
index 00000000..6263dfa8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bcat/IBcatService.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Bcat
+{
+ class IBcatService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IBcatService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/HOS/Services/Bcat/IDeliveryCacheStorageService.cs
new file mode 100644
index 00000000..7a55c6c8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bcat/IDeliveryCacheStorageService.cs
@@ -0,0 +1,21 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Bcat
+{
+ class IDeliveryCacheStorageService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IDeliveryCacheStorageService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs
new file mode 100644
index 00000000..55583c94
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs
@@ -0,0 +1,39 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Bcat
+{
+ class IServiceCreator : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IServiceCreator()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateBcatService },
+ { 1, CreateDeliveryCacheStorageService }
+ };
+ }
+
+ public long CreateBcatService(ServiceCtx Context)
+ {
+ long Id = Context.RequestData.ReadInt64();
+
+ MakeObject(Context, new IBcatService());
+
+ return 0;
+ }
+
+ public long CreateDeliveryCacheStorageService(ServiceCtx Context)
+ {
+ long Id = Context.RequestData.ReadInt64();
+
+ MakeObject(Context, new IDeliveryCacheStorageService());
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs b/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs
new file mode 100644
index 00000000..675edcc3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bsd/BsdError.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Bsd
+{
+ //bsd_errno == (SocketException.ErrorCode - 10000)
+ enum BsdError
+ {
+ Timeout = 60
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs b/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs
new file mode 100644
index 00000000..2361ed31
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bsd/BsdSocket.cs
@@ -0,0 +1,18 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace Ryujinx.HLE.HOS.Services.Bsd
+{
+ class BsdSocket
+ {
+ public int Family;
+ public int Type;
+ public int Protocol;
+
+ public IPAddress IpAddress;
+
+ public IPEndPoint RemoteEP;
+
+ public Socket Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs
new file mode 100644
index 00000000..b5a457db
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Bsd/IClient.cs
@@ -0,0 +1,445 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace Ryujinx.HLE.HOS.Services.Bsd
+{
+ class IClient : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private List<BsdSocket> Sockets = new List<BsdSocket>();
+
+ public IClient()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Initialize },
+ { 1, StartMonitoring },
+ { 2, Socket },
+ { 6, Poll },
+ { 8, Recv },
+ { 10, Send },
+ { 11, SendTo },
+ { 12, Accept },
+ { 13, Bind },
+ { 14, Connect },
+ { 18, Listen },
+ { 21, SetSockOpt },
+ { 26, Close }
+ };
+ }
+
+ //(u32, u32, u32, u32, u32, u32, u32, u32, u64 pid, u64 transferMemorySize, pid, KObject) -> u32 bsd_errno
+ public long Initialize(ServiceCtx Context)
+ {
+ /*
+ 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;
+ */
+
+ Context.ResponseData.Write(0);
+
+ //Todo: Stub
+
+ return 0;
+ }
+
+ //(u64, pid)
+ public long StartMonitoring(ServiceCtx Context)
+ {
+ //Todo: Stub
+
+ return 0;
+ }
+
+ //(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno)
+ public long Socket(ServiceCtx Context)
+ {
+ BsdSocket NewBsdSocket = new BsdSocket
+ {
+ Family = Context.RequestData.ReadInt32(),
+ Type = Context.RequestData.ReadInt32(),
+ Protocol = Context.RequestData.ReadInt32()
+ };
+
+ Sockets.Add(NewBsdSocket);
+
+ NewBsdSocket.Handle = new Socket((AddressFamily)NewBsdSocket.Family,
+ (SocketType)NewBsdSocket.Type,
+ (ProtocolType)NewBsdSocket.Protocol);
+
+ Context.ResponseData.Write(Sockets.Count - 1);
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ //(u32, u32, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno, buffer<unknown, 0x22, 0>)
+ public long Poll(ServiceCtx Context)
+ {
+ int PollCount = Context.RequestData.ReadInt32();
+ int TimeOut = Context.RequestData.ReadInt32();
+
+ //https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/poll.h
+ //https://msdn.microsoft.com/fr-fr/library/system.net.sockets.socket.poll(v=vs.110).aspx
+ //https://github.com/switchbrew/libnx/blob/e0457c4534b3c37426d83e1a620f82cb28c3b528/nx/source/services/bsd.c#L343
+ //https://github.com/TuxSH/ftpd/blob/switch_pr/source/ftp.c#L1634
+ //https://linux.die.net/man/2/poll
+
+ byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size);
+
+ int SocketId = Get32(SentBuffer, 0);
+ int RequestedEvents = Get16(SentBuffer, 4);
+ int ReturnedEvents = Get16(SentBuffer, 6);
+
+ //Todo: Stub - Need to implemented the Type-22 buffer.
+
+ Context.ResponseData.Write(1);
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ //(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, buffer<i8, 0x22, 0> message)
+ public long Recv(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+ int SocketFlags = Context.RequestData.ReadInt32();
+
+ byte[] ReceivedBuffer = new byte[Context.Request.ReceiveBuff[0].Size];
+
+ try
+ {
+ int BytesRead = Sockets[SocketId].Handle.Receive(ReceivedBuffer);
+
+ //Logging.Debug("Received Buffer:" + Environment.NewLine + Logging.HexDump(ReceivedBuffer));
+
+ Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, ReceivedBuffer);
+
+ Context.ResponseData.Write(BytesRead);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, u32 flags, buffer<i8, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
+ public long Send(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+ int SocketFlags = Context.RequestData.ReadInt32();
+
+ byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size);
+
+ try
+ {
+ //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer));
+
+ int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer);
+
+ Context.ResponseData.Write(BytesSent);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, u32 flags, buffer<i8, 0x21, 0>, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
+ public long SendTo(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+ int SocketFlags = Context.RequestData.ReadInt32();
+
+ byte[] SentBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size);
+
+ byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[1].Position,
+ Context.Request.SendBuff[1].Size);
+
+ if (!Sockets[SocketId].Handle.Connected)
+ {
+ try
+ {
+ ParseAddrBuffer(SocketId, AddressBuffer);
+
+ Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+ }
+
+ try
+ {
+ //Logging.Debug("Sent Buffer:" + Environment.NewLine + Logging.HexDump(SentBuffer));
+
+ int BytesSent = Sockets[SocketId].Handle.Send(SentBuffer);
+
+ Context.ResponseData.Write(BytesSent);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer<sockaddr, 0x22, 0> addr)
+ public long Accept(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+
+ long AddrBufferPtr = Context.Request.ReceiveBuff[0].Position;
+
+ Socket HandleAccept = null;
+
+ Task TimeOut = Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ HandleAccept = Sockets[SocketId].Handle.Accept();
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+ });
+
+ TimeOut.Wait(10000);
+
+ if (HandleAccept != null)
+ {
+ BsdSocket NewBsdSocket = new BsdSocket
+ {
+ IpAddress = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint).Address,
+ RemoteEP = ((IPEndPoint)Sockets[SocketId].Handle.LocalEndPoint),
+ Handle = HandleAccept
+ };
+
+ Sockets.Add(NewBsdSocket);
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ Writer.Write((byte)0);
+
+ Writer.Write((byte)NewBsdSocket.Handle.AddressFamily);
+
+ Writer.Write((short)((IPEndPoint)NewBsdSocket.Handle.LocalEndPoint).Port);
+
+ byte[] IpAddress = NewBsdSocket.IpAddress.GetAddressBytes();
+
+ Writer.Write(IpAddress);
+
+ Context.Memory.WriteBytes(AddrBufferPtr, MS.ToArray());
+
+ Context.ResponseData.Write(Sockets.Count - 1);
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(MS.Length);
+ }
+ }
+ else
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write((int)BsdError.Timeout);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
+ public long Bind(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+
+ byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size);
+
+ try
+ {
+ ParseAddrBuffer(SocketId, AddressBuffer);
+
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, buffer<sockaddr, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
+ public long Connect(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+
+ byte[] AddressBuffer = Context.Memory.ReadBytes(Context.Request.SendBuff[0].Position,
+ Context.Request.SendBuff[0].Size);
+
+ try
+ {
+ ParseAddrBuffer(SocketId, AddressBuffer);
+
+ Sockets[SocketId].Handle.Connect(Sockets[SocketId].RemoteEP);
+
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno)
+ public long Listen(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+ int BackLog = Context.RequestData.ReadInt32();
+
+ try
+ {
+ Sockets[SocketId].Handle.Bind(Sockets[SocketId].RemoteEP);
+ Sockets[SocketId].Handle.Listen(BackLog);
+
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket, u32 level, u32 option_name, buffer<unknown, 0x21, 0>) -> (i32 ret, u32 bsd_errno)
+ public long SetSockOpt(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+
+ SocketOptionLevel SocketLevel = (SocketOptionLevel)Context.RequestData.ReadInt32();
+ SocketOptionName SocketOptionName = (SocketOptionName)Context.RequestData.ReadInt32();
+
+ byte[] SocketOptionValue = Context.Memory.ReadBytes(Context.Request.PtrBuff[0].Position,
+ Context.Request.PtrBuff[0].Size);
+
+ int OptionValue = Get32(SocketOptionValue, 0);
+
+ try
+ {
+ Sockets[SocketId].Handle.SetSocketOption(SocketLevel, SocketOptionName, OptionValue);
+
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ //(u32 socket) -> (i32 ret, u32 bsd_errno)
+ public long Close(ServiceCtx Context)
+ {
+ int SocketId = Context.RequestData.ReadInt32();
+
+ try
+ {
+ Sockets[SocketId].Handle.Close();
+ Sockets[SocketId] = null;
+
+ Context.ResponseData.Write(0);
+ Context.ResponseData.Write(0);
+ }
+ catch (SocketException Ex)
+ {
+ Context.ResponseData.Write(-1);
+ Context.ResponseData.Write(Ex.ErrorCode - 10000);
+ }
+
+ return 0;
+ }
+
+ public void ParseAddrBuffer(int SocketId, byte[] AddrBuffer)
+ {
+ using (MemoryStream MS = new MemoryStream(AddrBuffer))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ int Size = Reader.ReadByte();
+ int Family = Reader.ReadByte();
+ int Port = EndianSwap.Swap16(Reader.ReadInt16());
+
+ string IpAddress = Reader.ReadByte().ToString() + "." +
+ Reader.ReadByte().ToString() + "." +
+ Reader.ReadByte().ToString() + "." +
+ Reader.ReadByte().ToString();
+
+ Sockets[SocketId].IpAddress = IPAddress.Parse(IpAddress);
+
+ Sockets[SocketId].RemoteEP = new IPEndPoint(Sockets[SocketId].IpAddress, Port);
+ }
+ }
+
+ private int Get16(byte[] Data, int Address)
+ {
+ return
+ Data[Address + 0] << 0 |
+ Data[Address + 1] << 8;
+ }
+
+ private int Get32(byte[] Data, int Address)
+ {
+ return
+ Data[Address + 0] << 0 |
+ Data[Address + 1] << 8 |
+ Data[Address + 2] << 16 |
+ Data[Address + 3] << 24;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs b/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs
new file mode 100644
index 00000000..6d96b769
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Caps
+{
+ class IAlbumAccessorService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAlbumAccessorService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs
new file mode 100644
index 00000000..5a54d563
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Caps
+{
+ class IScreenshotService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IScreenshotService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs
new file mode 100644
index 00000000..3b06ba8a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs
@@ -0,0 +1,49 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Friend
+{
+ class IFriendService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IFriendService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 10601, DeclareCloseOnlinePlaySession },
+ { 10610, UpdateUserPresence }
+ };
+ }
+
+ public long DeclareCloseOnlinePlaySession(ServiceCtx Context)
+ {
+ UserId Uuid = new UserId(
+ Context.RequestData.ReadInt64(),
+ Context.RequestData.ReadInt64());
+
+ if (Context.Device.System.State.TryGetUser(Uuid, out UserProfile Profile))
+ {
+ Profile.OnlinePlayState = OpenCloseState.Closed;
+ }
+
+ return 0;
+ }
+
+ public long UpdateUserPresence(ServiceCtx Context)
+ {
+ UserId Uuid = new UserId(
+ Context.RequestData.ReadInt64(),
+ Context.RequestData.ReadInt64());
+
+ //TODO.
+ Context.Device.Log.PrintStub(LogClass.ServiceFriend, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
new file mode 100644
index 00000000..065e230d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Friend
+{
+ class IServiceCreator : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IServiceCreator()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateFriendService }
+ };
+ }
+
+ public static long CreateFriendService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFriendService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs b/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs
new file mode 100644
index 00000000..39eadcec
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/FsErr.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ static class FsErr
+ {
+ public const int PathDoesNotExist = 1;
+ public const int PathAlreadyExists = 2;
+ public const int PathAlreadyInUse = 7;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs
new file mode 100644
index 00000000..d6ae084f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IDirectory.cs
@@ -0,0 +1,116 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ class IDirectory : IpcService, IDisposable
+ {
+ private const int DirectoryEntrySize = 0x310;
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private List<string> DirectoryEntries;
+
+ private int CurrentItemIndex;
+
+ public event EventHandler<EventArgs> Disposed;
+
+ public string HostPath { get; private set; }
+
+ public IDirectory(string HostPath, int Flags)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read },
+ { 1, GetEntryCount }
+ };
+
+ this.HostPath = HostPath;
+
+ DirectoryEntries = new List<string>();
+
+ if ((Flags & 1) != 0)
+ {
+ DirectoryEntries.AddRange(Directory.GetDirectories(HostPath));
+ }
+
+ if ((Flags & 2) != 0)
+ {
+ DirectoryEntries.AddRange(Directory.GetFiles(HostPath));
+ }
+
+ CurrentItemIndex = 0;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long BufferPosition = Context.Request.ReceiveBuff[0].Position;
+ long BufferLen = Context.Request.ReceiveBuff[0].Size;
+
+ int MaxReadCount = (int)(BufferLen / DirectoryEntrySize);
+
+ int Count = Math.Min(DirectoryEntries.Count - CurrentItemIndex, MaxReadCount);
+
+ for (int Index = 0; Index < Count; Index++)
+ {
+ long Position = BufferPosition + Index * DirectoryEntrySize;
+
+ WriteDirectoryEntry(Context, Position, DirectoryEntries[CurrentItemIndex++]);
+ }
+
+ Context.ResponseData.Write((long)Count);
+
+ return 0;
+ }
+
+ private void WriteDirectoryEntry(ServiceCtx Context, long Position, string FullPath)
+ {
+ for (int Offset = 0; Offset < 0x300; Offset += 8)
+ {
+ Context.Memory.WriteInt64(Position + Offset, 0);
+ }
+
+ byte[] NameBuffer = Encoding.UTF8.GetBytes(Path.GetFileName(FullPath));
+
+ Context.Memory.WriteBytes(Position, NameBuffer);
+
+ int Type = 0;
+ long Size = 0;
+
+ if (File.Exists(FullPath))
+ {
+ Type = 1;
+ Size = new FileInfo(FullPath).Length;
+ }
+
+ Context.Memory.WriteInt32(Position + 0x300, 0); //Padding?
+ Context.Memory.WriteInt32(Position + 0x304, Type);
+ Context.Memory.WriteInt64(Position + 0x308, Size);
+ }
+
+ public long GetEntryCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)DirectoryEntries.Count);
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Disposed?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs
new file mode 100644
index 00000000..194d9420
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFile.cs
@@ -0,0 +1,110 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ class IFile : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private Stream BaseStream;
+
+ public event EventHandler<EventArgs> Disposed;
+
+ public string HostPath { get; private set; }
+
+ public IFile(Stream BaseStream, string HostPath)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read },
+ { 1, Write },
+ { 2, Flush },
+ { 3, SetSize },
+ { 4, GetSize }
+ };
+
+ this.BaseStream = BaseStream;
+ this.HostPath = HostPath;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long Position = Context.Request.ReceiveBuff[0].Position;
+
+ long Zero = Context.RequestData.ReadInt64();
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ byte[] Data = new byte[Size];
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+
+ int ReadSize = BaseStream.Read(Data, 0, (int)Size);
+
+ Context.Memory.WriteBytes(Position, Data);
+
+ Context.ResponseData.Write((long)ReadSize);
+
+ return 0;
+ }
+
+ public long Write(ServiceCtx Context)
+ {
+ long Position = Context.Request.SendBuff[0].Position;
+
+ long Zero = Context.RequestData.ReadInt64();
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ byte[] Data = Context.Memory.ReadBytes(Position, Size);
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+ BaseStream.Write(Data, 0, (int)Size);
+
+ return 0;
+ }
+
+ public long Flush(ServiceCtx Context)
+ {
+ BaseStream.Flush();
+
+ return 0;
+ }
+
+ public long SetSize(ServiceCtx Context)
+ {
+ long Size = Context.RequestData.ReadInt64();
+
+ BaseStream.SetLength(Size);
+
+ return 0;
+ }
+
+ public long GetSize(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(BaseStream.Length);
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && BaseStream != null)
+ {
+ BaseStream.Dispose();
+
+ Disposed?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
new file mode 100644
index 00000000..b77043bd
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
@@ -0,0 +1,412 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ class IFileSystem : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private HashSet<string> OpenPaths;
+
+ private string Path;
+
+ public IFileSystem(string Path)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateFile },
+ { 1, DeleteFile },
+ { 2, CreateDirectory },
+ { 3, DeleteDirectory },
+ { 4, DeleteDirectoryRecursively },
+ { 5, RenameFile },
+ { 6, RenameDirectory },
+ { 7, GetEntryType },
+ { 8, OpenFile },
+ { 9, OpenDirectory },
+ { 10, Commit },
+ { 11, GetFreeSpaceSize },
+ { 12, GetTotalSpaceSize },
+ { 13, CleanDirectoryRecursively },
+ //{ 14, GetFileTimeStampRaw }
+ };
+
+ OpenPaths = new HashSet<string>();
+
+ this.Path = Path;
+ }
+
+ public long CreateFile(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ long Mode = Context.RequestData.ReadInt64();
+ int Size = Context.RequestData.ReadInt32();
+
+ string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (FileName == null)
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (File.Exists(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ if (IsPathAlreadyInUse(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ using (FileStream NewFile = File.Create(FileName))
+ {
+ NewFile.SetLength(Size);
+ }
+
+ return 0;
+ }
+
+ public long DeleteFile(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (!File.Exists(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (IsPathAlreadyInUse(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ File.Delete(FileName);
+
+ return 0;
+ }
+
+ public long CreateDirectory(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (DirName == null)
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (Directory.Exists(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ if (IsPathAlreadyInUse(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ Directory.CreateDirectory(DirName);
+
+ return 0;
+ }
+
+ public long DeleteDirectory(ServiceCtx Context)
+ {
+ return DeleteDirectory(Context, false);
+ }
+
+ public long DeleteDirectoryRecursively(ServiceCtx Context)
+ {
+ return DeleteDirectory(Context, true);
+ }
+
+ private long DeleteDirectory(ServiceCtx Context, bool Recursive)
+ {
+ string Name = ReadUtf8String(Context);
+
+ string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (!Directory.Exists(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (IsPathAlreadyInUse(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ Directory.Delete(DirName, Recursive);
+
+ return 0;
+ }
+
+ public long RenameFile(ServiceCtx Context)
+ {
+ string OldName = ReadUtf8String(Context, 0);
+ string NewName = ReadUtf8String(Context, 1);
+
+ string OldFileName = Context.Device.FileSystem.GetFullPath(Path, OldName);
+ string NewFileName = Context.Device.FileSystem.GetFullPath(Path, NewName);
+
+ if (!File.Exists(OldFileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (File.Exists(NewFileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ if (IsPathAlreadyInUse(OldFileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ File.Move(OldFileName, NewFileName);
+
+ return 0;
+ }
+
+ public long RenameDirectory(ServiceCtx Context)
+ {
+ string OldName = ReadUtf8String(Context, 0);
+ string NewName = ReadUtf8String(Context, 1);
+
+ string OldDirName = Context.Device.FileSystem.GetFullPath(Path, OldName);
+ string NewDirName = Context.Device.FileSystem.GetFullPath(Path, NewName);
+
+ if (!Directory.Exists(OldDirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (Directory.Exists(NewDirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists);
+ }
+
+ if (IsPathAlreadyInUse(OldDirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ Directory.Move(OldDirName, NewDirName);
+
+ return 0;
+ }
+
+ public long GetEntryType(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (File.Exists(FileName))
+ {
+ Context.ResponseData.Write(1);
+ }
+ else if (Directory.Exists(FileName))
+ {
+ Context.ResponseData.Write(0);
+ }
+ else
+ {
+ Context.ResponseData.Write(0);
+
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ return 0;
+ }
+
+ public long OpenFile(ServiceCtx Context)
+ {
+ int FilterFlags = Context.RequestData.ReadInt32();
+
+ string Name = ReadUtf8String(Context);
+
+ string FileName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (!File.Exists(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (IsPathAlreadyInUse(FileName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ FileStream Stream = new FileStream(FileName, FileMode.Open);
+
+ IFile FileInterface = new IFile(Stream, FileName);
+
+ FileInterface.Disposed += RemoveFileInUse;
+
+ lock (OpenPaths)
+ {
+ OpenPaths.Add(FileName);
+ }
+
+ MakeObject(Context, FileInterface);
+
+ return 0;
+ }
+
+ public long OpenDirectory(ServiceCtx Context)
+ {
+ int FilterFlags = Context.RequestData.ReadInt32();
+
+ string Name = ReadUtf8String(Context);
+
+ string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (!Directory.Exists(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (IsPathAlreadyInUse(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ IDirectory DirInterface = new IDirectory(DirName, FilterFlags);
+
+ DirInterface.Disposed += RemoveDirectoryInUse;
+
+ lock (OpenPaths)
+ {
+ OpenPaths.Add(DirName);
+ }
+
+ MakeObject(Context, DirInterface);
+
+ return 0;
+ }
+
+ public long Commit(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long GetFreeSpaceSize(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().AvailableFreeSpace);
+
+ return 0;
+ }
+
+ public long GetTotalSpaceSize(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ Context.ResponseData.Write(Context.Device.FileSystem.GetDrive().TotalSize);
+
+ return 0;
+ }
+
+ public long CleanDirectoryRecursively(ServiceCtx Context)
+ {
+ string Name = ReadUtf8String(Context);
+
+ string DirName = Context.Device.FileSystem.GetFullPath(Path, Name);
+
+ if (!Directory.Exists(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+ }
+
+ if (IsPathAlreadyInUse(DirName))
+ {
+ return MakeError(ErrorModule.Fs, FsErr.PathAlreadyInUse);
+ }
+
+ foreach (string Entry in Directory.EnumerateFileSystemEntries(DirName))
+ {
+ if (Directory.Exists(Entry))
+ {
+ Directory.Delete(Entry, true);
+ }
+ else if (File.Exists(Entry))
+ {
+ File.Delete(Entry);
+ }
+ }
+
+ return 0;
+ }
+
+ private bool IsPathAlreadyInUse(string Path)
+ {
+ lock (OpenPaths)
+ {
+ return OpenPaths.Contains(Path);
+ }
+ }
+
+ private void RemoveFileInUse(object sender, EventArgs e)
+ {
+ IFile FileInterface = (IFile)sender;
+
+ lock (OpenPaths)
+ {
+ FileInterface.Disposed -= RemoveFileInUse;
+
+ OpenPaths.Remove(FileInterface.HostPath);
+ }
+ }
+
+ private void RemoveDirectoryInUse(object sender, EventArgs e)
+ {
+ IDirectory DirInterface = (IDirectory)sender;
+
+ lock (OpenPaths)
+ {
+ DirInterface.Disposed -= RemoveDirectoryInUse;
+
+ OpenPaths.Remove(DirInterface.HostPath);
+ }
+ }
+
+ private string ReadUtf8String(ServiceCtx Context, int Index = 0)
+ {
+ long Position = Context.Request.PtrBuff[Index].Position;
+ long Size = Context.Request.PtrBuff[Index].Size;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ while (Size-- > 0)
+ {
+ byte Value = Context.Memory.ReadByte(Position++);
+
+ if (Value == 0)
+ {
+ break;
+ }
+
+ MS.WriteByte(Value);
+ }
+
+ return Encoding.UTF8.GetString(MS.ToArray());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
new file mode 100644
index 00000000..14edcc75
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
@@ -0,0 +1,74 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ class IFileSystemProxy : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IFileSystemProxy()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, SetCurrentProcess },
+ { 18, OpenSdCardFileSystem },
+ { 22, CreateSaveDataFileSystem },
+ { 51, OpenSaveDataFileSystem },
+ { 200, OpenDataStorageByCurrentProcess },
+ { 203, OpenPatchDataStorageByCurrentProcess },
+ { 1005, GetGlobalAccessLogMode }
+ };
+ }
+
+ public long SetCurrentProcess(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long OpenSdCardFileSystem(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetSdCardPath()));
+
+ return 0;
+ }
+
+ public long CreateSaveDataFileSystem(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceFs, "Stubbed.");
+
+ return 0;
+ }
+
+ public long OpenSaveDataFileSystem(ServiceCtx Context)
+ {
+ MakeObject(Context, new IFileSystem(Context.Device.FileSystem.GetGameSavesPath()));
+
+ return 0;
+ }
+
+ public long OpenDataStorageByCurrentProcess(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
+
+ return 0;
+ }
+
+ public long OpenPatchDataStorageByCurrentProcess(ServiceCtx Context)
+ {
+ MakeObject(Context, new IStorage(Context.Device.FileSystem.RomFs));
+
+ return 0;
+ }
+
+ public long GetGlobalAccessLogMode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs
new file mode 100644
index 00000000..5118fa45
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IStorage.cs
@@ -0,0 +1,51 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+ class IStorage : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private Stream BaseStream;
+
+ public IStorage(Stream BaseStream)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Read }
+ };
+
+ this.BaseStream = BaseStream;
+ }
+
+ public long Read(ServiceCtx Context)
+ {
+ long Offset = Context.RequestData.ReadInt64();
+ long Size = Context.RequestData.ReadInt64();
+
+ if (Context.Request.ReceiveBuff.Count > 0)
+ {
+ IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0];
+
+ //Use smaller length to avoid overflows.
+ if (Size > BuffDesc.Size)
+ {
+ Size = BuffDesc.Size;
+ }
+
+ byte[] Data = new byte[Size];
+
+ BaseStream.Seek(Offset, SeekOrigin.Begin);
+ BaseStream.Read(Data, 0, Data.Length);
+
+ Context.Memory.WriteBytes(BuffDesc.Position, Data);
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/IActiveVibrationDeviceList.cs b/Ryujinx.HLE/HOS/Services/Hid/IActiveVibrationDeviceList.cs
new file mode 100644
index 00000000..6e666919
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Hid/IActiveVibrationDeviceList.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ class IActiveApplicationDeviceList : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IActiveApplicationDeviceList()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, ActivateVibrationDevice }
+ };
+ }
+
+ public long ActivateVibrationDevice(ServiceCtx Context)
+ {
+ int VibrationDeviceHandle = Context.RequestData.ReadInt32();
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs
new file mode 100644
index 00000000..012ccb40
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs
@@ -0,0 +1,34 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ class IAppletResource : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KSharedMemory HidSharedMem;
+
+ public IAppletResource(KSharedMemory HidSharedMem)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetSharedMemoryHandle }
+ };
+
+ this.HidSharedMem = HidSharedMem;
+ }
+
+ public long GetSharedMemoryHandle(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(HidSharedMem);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
new file mode 100644
index 00000000..70f1f1f1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -0,0 +1,299 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Input;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ class IHidServer : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ private KEvent NpadStyleSetUpdateEvent;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IHidServer()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateAppletResource },
+ { 1, ActivateDebugPad },
+ { 11, ActivateTouchScreen },
+ { 21, ActivateMouse },
+ { 31, ActivateKeyboard },
+ { 66, StartSixAxisSensor },
+ { 79, SetGyroscopeZeroDriftMode },
+ { 100, SetSupportedNpadStyleSet },
+ { 101, GetSupportedNpadStyleSet },
+ { 102, SetSupportedNpadIdType },
+ { 103, ActivateNpad },
+ { 106, AcquireNpadStyleSetUpdateEventHandle },
+ { 108, GetPlayerLedPattern },
+ { 120, SetNpadJoyHoldType },
+ { 121, GetNpadJoyHoldType },
+ { 122, SetNpadJoyAssignmentModeSingleByDefault },
+ { 123, SetNpadJoyAssignmentModeSingle },
+ { 124, SetNpadJoyAssignmentModeDual },
+ { 125, MergeSingleJoyAsDualJoy },
+ { 128, SetNpadHandheldActivationMode },
+ { 200, GetVibrationDeviceInfo },
+ { 201, SendVibrationValue },
+ { 203, CreateActiveVibrationDeviceList },
+ { 206, SendVibrationValues }
+ };
+
+ NpadStyleSetUpdateEvent = new KEvent();
+ }
+
+ public long CreateAppletResource(ServiceCtx Context)
+ {
+ MakeObject(Context, new IAppletResource(Context.Device.System.HidSharedMem));
+
+ return 0;
+ }
+
+ public long ActivateDebugPad(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ActivateTouchScreen(ServiceCtx Context)
+ {
+ long AppletResourceUserId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ActivateMouse(ServiceCtx Context)
+ {
+ long AppletResourceUserId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ActivateKeyboard(ServiceCtx Context)
+ {
+ long AppletResourceUserId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long StartSixAxisSensor(ServiceCtx Context)
+ {
+ int Handle = Context.RequestData.ReadInt32();
+
+ long AppletResourceUserId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetGyroscopeZeroDriftMode(ServiceCtx Context)
+ {
+ int Handle = Context.RequestData.ReadInt32();
+ int Unknown = Context.RequestData.ReadInt32();
+ long AppletResourceUserId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long AcquireNpadStyleSetUpdateEventHandle(ServiceCtx Context)
+ {
+ int Handle = Context.Process.HandleTable.OpenHandle(NpadStyleSetUpdateEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long GetSupportedNpadStyleSet(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetSupportedNpadStyleSet(ServiceCtx Context)
+ {
+ long Unknown0 = Context.RequestData.ReadInt64();
+ long Unknown8 = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetSupportedNpadIdType(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long ActivateNpad(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetPlayerLedPattern(ServiceCtx Context)
+ {
+ long Unknown = Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write(0L);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetNpadJoyHoldType(ServiceCtx Context)
+ {
+ long Unknown0 = Context.RequestData.ReadInt64();
+ long Unknown8 = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetNpadJoyHoldType(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0L);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx Context)
+ {
+ HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
+
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetNpadJoyAssignmentModeSingle(ServiceCtx Context)
+ {
+ HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
+
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+ long NpadJoyDeviceType = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetNpadJoyAssignmentModeDual(ServiceCtx Context)
+ {
+ HidControllerId HidControllerId = (HidControllerId)Context.RequestData.ReadInt32();
+
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long MergeSingleJoyAsDualJoy(ServiceCtx Context)
+ {
+ long Unknown0 = Context.RequestData.ReadInt32();
+ long Unknown8 = Context.RequestData.ReadInt32();
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetNpadHandheldActivationMode(ServiceCtx Context)
+ {
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+ long Unknown = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetVibrationDeviceInfo(ServiceCtx Context)
+ {
+ int VibrationDeviceHandle = Context.RequestData.ReadInt32();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ Context.ResponseData.Write(0L); //VibrationDeviceInfoForIpc
+
+ return 0;
+ }
+
+ public long SendVibrationValue(ServiceCtx Context)
+ {
+ int VibrationDeviceHandle = Context.RequestData.ReadInt32();
+
+ int VibrationValue1 = Context.RequestData.ReadInt32();
+ int VibrationValue2 = Context.RequestData.ReadInt32();
+ int VibrationValue3 = Context.RequestData.ReadInt32();
+ int VibrationValue4 = Context.RequestData.ReadInt32();
+
+ long AppletUserResourceId = Context.RequestData.ReadInt64();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public long CreateActiveVibrationDeviceList(ServiceCtx Context)
+ {
+ MakeObject(Context, new IActiveApplicationDeviceList());
+
+ return 0;
+ }
+
+ public long SendVibrationValues(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceHid, "Stubbed.");
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ NpadStyleSetUpdateEvent.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/IIpcService.cs b/Ryujinx.HLE/HOS/Services/IIpcService.cs
new file mode 100644
index 00000000..f6bafe97
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/IIpcService.cs
@@ -0,0 +1,10 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services
+{
+ interface IIpcService
+ {
+ IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs
new file mode 100644
index 00000000..8e487d55
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/IpcService.cs
@@ -0,0 +1,183 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services
+{
+ abstract class IpcService : IIpcService
+ {
+ public abstract IReadOnlyDictionary<int, ServiceProcessRequest> Commands { get; }
+
+ private IdDictionary DomainObjects;
+
+ private int SelfId;
+
+ private bool IsDomain;
+
+ public IpcService()
+ {
+ 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 CallMethod(ServiceCtx Context)
+ {
+ IIpcService 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);
+
+ 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();
+
+ if (Service.Commands.TryGetValue(CommandId, out ServiceProcessRequest ProcessRequest))
+ {
+ Context.ResponseData.BaseStream.Seek(IsDomain ? 0x20 : 0x10, SeekOrigin.Begin);
+
+ Context.Device.Log.PrintDebug(LogClass.KernelIpc, $"{Service.GetType().Name}: {ProcessRequest.Method.Name}");
+
+ long Result = ProcessRequest(Context);
+
+ 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(Result);
+ }
+ else
+ {
+ string DbgMessage = $"{Context.Session.ServiceName} {Service.GetType().Name}: {CommandId}";
+
+ throw new NotImplementedException(DbgMessage);
+ }
+ }
+
+ protected static void MakeObject(ServiceCtx Context, IpcService Obj)
+ {
+ IpcService Service = Context.Session.Service;
+
+ if (Service.IsDomain)
+ {
+ Context.Response.ObjectIds.Add(Service.Add(Obj));
+ }
+ else
+ {
+ KSession Session = new KSession(Obj, Context.Session.ServiceName);
+
+ int Handle = Context.Process.HandleTable.OpenHandle(Session);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+ }
+ }
+
+ protected static T GetObject<T>(ServiceCtx Context, int Index) where T : IpcService
+ {
+ IpcService Service = Context.Session.Service;
+
+ if (!Service.IsDomain)
+ {
+ int Handle = Context.Request.HandleDesc.ToMove[Index];
+
+ KSession Session = Context.Process.HandleTable.GetData<KSession>(Handle);
+
+ return Session?.Service is T ? (T)Session.Service : null;
+ }
+
+ int ObjId = Context.Request.ObjectIds[Index];
+
+ IIpcService Obj = Service.GetObject(ObjId);
+
+ return Obj is T ? (T)Obj : null;
+ }
+
+ private int Add(IIpcService 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 IIpcService GetObject(int Id)
+ {
+ return DomainObjects.GetData<IIpcService>(Id);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs
new file mode 100644
index 00000000..d79da515
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lm/ILogService.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Lm
+{
+ class ILogService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ILogService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Initialize }
+ };
+ }
+
+ public long Initialize(ServiceCtx Context)
+ {
+ MakeObject(Context, new ILogger());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs
new file mode 100644
index 00000000..42d57c6f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lm/ILogger.cs
@@ -0,0 +1,86 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Lm
+{
+ class ILogger : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ILogger()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Log }
+ };
+ }
+
+ public long Log(ServiceCtx Context)
+ {
+ byte[] LogBuffer = Context.Memory.ReadBytes(
+ Context.Request.PtrBuff[0].Position,
+ Context.Request.PtrBuff[0].Size);
+
+ using (MemoryStream MS = new MemoryStream(LogBuffer))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ long Pid = Reader.ReadInt64();
+ long ThreadContext = Reader.ReadInt64();
+ short Flags = Reader.ReadInt16();
+ byte Level = Reader.ReadByte();
+ byte Verbosity = Reader.ReadByte();
+ int PayloadLength = Reader.ReadInt32();
+
+ StringBuilder SB = new StringBuilder();
+
+ SB.AppendLine("Guest log:");
+
+ while (MS.Position < MS.Length)
+ {
+ byte Type = Reader.ReadByte();
+ byte Size = Reader.ReadByte();
+
+ LmLogField Field = (LmLogField)Type;
+
+ string FieldStr = string.Empty;
+
+ if (Field == LmLogField.Skip)
+ {
+ Reader.ReadByte();
+
+ continue;
+ }
+ else if (Field == LmLogField.Line)
+ {
+ FieldStr = Field + ": " + Reader.ReadInt32();
+ }
+ else
+ {
+ FieldStr = Field + ": \"" + Encoding.UTF8.GetString(Reader.ReadBytes(Size)) + "\"";
+ }
+
+ SB.AppendLine(" " + FieldStr);
+ }
+
+ string Text = SB.ToString();
+
+ switch((LmLogLevel)Level)
+ {
+ case LmLogLevel.Trace: Context.Device.Log.PrintDebug (LogClass.ServiceLm, Text); break;
+ case LmLogLevel.Info: Context.Device.Log.PrintInfo (LogClass.ServiceLm, Text); break;
+ case LmLogLevel.Warning: Context.Device.Log.PrintWarning(LogClass.ServiceLm, Text); break;
+ case LmLogLevel.Error: Context.Device.Log.PrintError (LogClass.ServiceLm, Text); break;
+ case LmLogLevel.Critical: Context.Device.Log.PrintError (LogClass.ServiceLm, Text); break;
+ }
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs b/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs
new file mode 100644
index 00000000..bd8c8e88
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lm/LmLogField.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Lm
+{
+ enum LmLogField
+ {
+ Skip = 1,
+ Message = 2,
+ Line = 3,
+ Filename = 4,
+ Function = 5,
+ Module = 6,
+ Thread = 7
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Lm/LmLogLevel.cs b/Ryujinx.HLE/HOS/Services/Lm/LmLogLevel.cs
new file mode 100644
index 00000000..70554c42
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lm/LmLogLevel.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Lm
+{
+ enum LmLogLevel
+ {
+ Trace,
+ Info,
+ Warning,
+ Error,
+ Critical
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs
new file mode 100644
index 00000000..e65ef086
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs
@@ -0,0 +1,46 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Mm
+{
+ class IRequest : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IRequest()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, Initialize },
+ { 6, SetAndWait },
+ { 7, Get }
+ };
+ }
+
+ public long Initialize(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetAndWait(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long Get(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceMm, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfp/DeviceState.cs b/Ryujinx.HLE/HOS/Services/Nfp/DeviceState.cs
new file mode 100644
index 00000000..04527893
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfp/DeviceState.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nfp
+{
+ enum DeviceState
+ {
+ Initialized = 0
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs
new file mode 100644
index 00000000..eac90da4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs
@@ -0,0 +1,114 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Input;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nfp
+{
+ class IUser : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private const HidControllerId NpadId = HidControllerId.CONTROLLER_PLAYER_1;
+
+ private State State = State.NonInitialized;
+
+ private DeviceState DeviceState = DeviceState.Initialized;
+
+ private KEvent ActivateEvent;
+
+ private KEvent DeactivateEvent;
+
+ private KEvent AvailabilityChangeEvent;
+
+ public IUser()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Initialize },
+ { 17, AttachActivateEvent },
+ { 18, AttachDeactivateEvent },
+ { 19, GetState },
+ { 20, GetDeviceState },
+ { 21, GetNpadId },
+ { 23, AttachAvailabilityChangeEvent }
+ };
+
+ ActivateEvent = new KEvent();
+ DeactivateEvent = new KEvent();
+ AvailabilityChangeEvent = new KEvent();
+ }
+
+ public long Initialize(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ State = State.Initialized;
+
+ return 0;
+ }
+
+ public long AttachActivateEvent(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ int Handle = Context.Process.HandleTable.OpenHandle(ActivateEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);;
+
+ return 0;
+ }
+
+ public long AttachDeactivateEvent(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ int Handle = Context.Process.HandleTable.OpenHandle(DeactivateEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long GetState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)State);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetDeviceState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)DeviceState);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetNpadId(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)NpadId);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ return 0;
+ }
+
+ public long AttachAvailabilityChangeEvent(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNfp, "Stubbed.");
+
+ int Handle = Context.Process.HandleTable.OpenHandle(AvailabilityChangeEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs
new file mode 100644
index 00000000..770f0341
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfp/IUserManager.cs
@@ -0,0 +1,27 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nfp
+{
+ class IUserManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IUserManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetUserInterface }
+ };
+ }
+
+ public long GetUserInterface(ServiceCtx Context)
+ {
+ MakeObject(Context, new IUser());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfp/State.cs b/Ryujinx.HLE/HOS/Services/Nfp/State.cs
new file mode 100644
index 00000000..0b4b3c1b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfp/State.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfp
+{
+ enum State
+ {
+ NonInitialized = 0,
+ Initialized = 1
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs
new file mode 100644
index 00000000..ec68247b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nifm/IGeneralService.cs
@@ -0,0 +1,58 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Nifm
+{
+ class IGeneralService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IGeneralService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, CreateRequest },
+ { 12, GetCurrentIpAddress }
+ };
+ }
+
+ public long CreateRequest(ServiceCtx Context)
+ {
+ int Unknown = Context.RequestData.ReadInt32();
+
+ MakeObject(Context, new IRequest());
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetCurrentIpAddress(ServiceCtx Context)
+ {
+ if (!NetworkInterface.GetIsNetworkAvailable())
+ {
+ return MakeError(ErrorModule.Nifm, NifmErr.NoInternetConnection);
+ }
+
+ IPHostEntry Host = Dns.GetHostEntry(Dns.GetHostName());
+
+ IPAddress Address = Host.AddressList.FirstOrDefault(A => A.AddressFamily == AddressFamily.InterNetwork);
+
+ Context.ResponseData.Write(BitConverter.ToUInt32(Address.GetAddressBytes()));
+
+ Context.Device.Log.PrintInfo(LogClass.ServiceNifm, $"Console's local IP is \"{Address}\".");
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs
new file mode 100644
index 00000000..7bd30ff9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs
@@ -0,0 +1,95 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nifm
+{
+ class IRequest : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent Event0;
+ private KEvent Event1;
+
+ public IRequest()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetRequestState },
+ { 1, GetResult },
+ { 2, GetSystemEventReadableHandles },
+ { 3, Cancel },
+ { 4, Submit },
+ { 11, SetConnectionConfirmationOption }
+ };
+
+ Event0 = new KEvent();
+ Event1 = new KEvent();
+ }
+
+ public long GetRequestState(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(1);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetResult(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long GetSystemEventReadableHandles(ServiceCtx Context)
+ {
+ int Handle0 = Context.Process.HandleTable.OpenHandle(Event0);
+ int Handle1 = Context.Process.HandleTable.OpenHandle(Event1);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle0, Handle1);
+
+ return 0;
+ }
+
+ public long Cancel(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long Submit(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public long SetConnectionConfirmationOption(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNifm, "Stubbed.");
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ Event0.Dispose();
+ Event1.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs
new file mode 100644
index 00000000..19e7d247
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nifm
+{
+ class IStaticService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IStaticService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, CreateGeneralServiceOld },
+ { 5, CreateGeneralService }
+ };
+ }
+
+ public long CreateGeneralServiceOld(ServiceCtx Context)
+ {
+ MakeObject(Context, new IGeneralService());
+
+ return 0;
+ }
+
+ public long CreateGeneralService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IGeneralService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nifm/NifmErr.cs b/Ryujinx.HLE/HOS/Services/Nifm/NifmErr.cs
new file mode 100644
index 00000000..3abeb838
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nifm/NifmErr.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nifm
+{
+ static class NifmErr
+ {
+ public const int NoInternetConnection = 300;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs
new file mode 100644
index 00000000..b8455d41
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ns/IAddOnContentManager.cs
@@ -0,0 +1,42 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class IAddOnContentManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IAddOnContentManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2, CountAddOnContent },
+ { 3, ListAddOnContent }
+ };
+ }
+
+ public static long CountAddOnContent(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(0);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed.");
+
+ return 0;
+ }
+
+ public static long ListAddOnContent(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNs, "Stubbed.");
+
+ //TODO: This is supposed to write a u32 array aswell.
+ //It's unknown what it contains.
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
new file mode 100644
index 00000000..a27cfe60
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class IServiceGetterInterface : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IServiceGetterInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs
new file mode 100644
index 00000000..6ade8865
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class ISystemUpdateInterface : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemUpdateInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs
new file mode 100644
index 00000000..cff334c0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class IVulnerabilityManagerInterface : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IVulnerabilityManagerInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
new file mode 100644
index 00000000..bfc76931
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs
@@ -0,0 +1,231 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS;
+using Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu;
+using Ryujinx.HLE.HOS.Services.Nv.NvHostChannel;
+using Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl;
+using Ryujinx.HLE.HOS.Services.Nv.NvMap;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nv
+{
+ class INvDrvServices : IpcService, IDisposable
+ {
+ private delegate int IoctlProcessor(ServiceCtx Context, int Cmd);
+
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private static Dictionary<string, IoctlProcessor> IoctlProcessors =
+ new Dictionary<string, IoctlProcessor>()
+ {
+ { "/dev/nvhost-as-gpu", ProcessIoctlNvGpuAS },
+ { "/dev/nvhost-ctrl", ProcessIoctlNvHostCtrl },
+ { "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu },
+ { "/dev/nvhost-gpu", ProcessIoctlNvHostGpu },
+ { "/dev/nvmap", ProcessIoctlNvMap }
+ };
+
+ public static GlobalStateTable Fds { get; private set; }
+
+ private KEvent Event;
+
+ public INvDrvServices()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Open },
+ { 1, Ioctl },
+ { 2, Close },
+ { 3, Initialize },
+ { 4, QueryEvent },
+ { 8, SetClientPid },
+ { 11, Ioctl },
+ { 13, FinishInitialize }
+ };
+
+ Event = new KEvent();
+ }
+
+ static INvDrvServices()
+ {
+ Fds = new GlobalStateTable();
+ }
+
+ public long Open(ServiceCtx Context)
+ {
+ long NamePtr = Context.Request.SendBuff[0].Position;
+
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr);
+
+ int Fd = Fds.Add(Context.Process, new NvFd(Name));
+
+ Context.ResponseData.Write(Fd);
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public long Ioctl(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+ int Cmd = Context.RequestData.ReadInt32();
+
+ NvFd FdData = Fds.GetData<NvFd>(Context.Process, Fd);
+
+ int Result;
+
+ if (IoctlProcessors.TryGetValue(FdData.Name, out IoctlProcessor Process))
+ {
+ Result = Process(Context, Cmd);
+ }
+ else
+ {
+ throw new NotImplementedException($"{FdData.Name} {Cmd:x4}");
+ }
+
+ //TODO: Verify if the error codes needs to be translated.
+ Context.ResponseData.Write(Result);
+
+ return 0;
+ }
+
+ public long Close(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+
+ Fds.Delete(Context.Process, Fd);
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public long Initialize(ServiceCtx Context)
+ {
+ long TransferMemSize = Context.RequestData.ReadInt64();
+ int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0];
+
+ NvMapIoctl.InitializeNvMap(Context);
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public long QueryEvent(ServiceCtx Context)
+ {
+ int Fd = Context.RequestData.ReadInt32();
+ int EventId = Context.RequestData.ReadInt32();
+
+ //TODO: Use Fd/EventId, different channels have different events.
+ int Handle = Context.Process.HandleTable.OpenHandle(Event);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public long SetClientPid(ServiceCtx Context)
+ {
+ long Pid = Context.RequestData.ReadInt64();
+
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+
+ public long FinishInitialize(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return 0;
+ }
+
+ private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl);
+ }
+
+ private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl);
+ }
+
+ private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl);
+ }
+
+ private static int ProcessIoctlNvHostGpu(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctlGpu);
+ }
+
+ private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl);
+ }
+
+ private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor)
+ {
+ if (CmdIn(Cmd) && Context.Request.GetBufferType0x21().Position == 0)
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if (CmdOut(Cmd) && Context.Request.GetBufferType0x22().Position == 0)
+ {
+ Context.Device.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!");
+
+ return NvResult.InvalidInput;
+ }
+
+ return Processor(Context, Cmd);
+ }
+
+ private static bool CmdIn(int Cmd)
+ {
+ return ((Cmd >> 30) & 1) != 0;
+ }
+
+ private static bool CmdOut(int Cmd)
+ {
+ return ((Cmd >> 31) & 1) != 0;
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ Fds.DeleteProcess(Process);
+
+ NvGpuASIoctl.UnloadProcess(Process);
+
+ NvHostChannelIoctl.UnloadProcess(Process);
+
+ NvHostCtrlIoctl.UnloadProcess(Process);
+
+ NvMapIoctl.UnloadProcess(Process);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ Event.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvFd.cs b/Ryujinx.HLE/HOS/Services/Nv/NvFd.cs
new file mode 100644
index 00000000..96f97f41
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvFd.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Nv
+{
+ class NvFd
+ {
+ public string Name { get; private set; }
+
+ public NvFd(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs
new file mode 100644
index 00000000..bca2f9f4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ struct NvGpuASAllocSpace
+ {
+ public int Pages;
+ public int PageSize;
+ public int Flags;
+ public int Padding;
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs
new file mode 100644
index 00000000..d69ec719
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASCtx.cs
@@ -0,0 +1,197 @@
+using Ryujinx.HLE.Gpu.Memory;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ class NvGpuASCtx
+ {
+ public NvGpuVmm Vmm { get; private set; }
+
+ private class Range
+ {
+ public ulong Start { get; private set; }
+ public ulong End { get; private set; }
+
+ public Range(long Position, long Size)
+ {
+ Start = (ulong)Position;
+ End = (ulong)Size + Start;
+ }
+ }
+
+ private class MappedMemory : Range
+ {
+ public long PhysicalAddress { get; private set; }
+ public bool VaAllocated { get; private set; }
+
+ public MappedMemory(
+ long Position,
+ long Size,
+ long PhysicalAddress,
+ bool VaAllocated) : base(Position, Size)
+ {
+ this.PhysicalAddress = PhysicalAddress;
+ this.VaAllocated = VaAllocated;
+ }
+ }
+
+ private SortedList<long, Range> Maps;
+ private SortedList<long, Range> Reservations;
+
+ public NvGpuASCtx(ServiceCtx Context)
+ {
+ Vmm = new NvGpuVmm(Context.Memory);
+
+ Maps = new SortedList<long, Range>();
+ Reservations = new SortedList<long, Range>();
+ }
+
+ public bool ValidateFixedBuffer(long Position, long Size)
+ {
+ long MapEnd = Position + Size;
+
+ //Check if size is valid (0 is also not allowed).
+ if ((ulong)MapEnd <= (ulong)Position)
+ {
+ return false;
+ }
+
+ //Check if address is page aligned.
+ if ((Position & NvGpuVmm.PageMask) != 0)
+ {
+ return false;
+ }
+
+ //Check if region is reserved.
+ if (BinarySearch(Reservations, Position) == null)
+ {
+ return false;
+ }
+
+ //Check for overlap with already mapped buffers.
+ Range Map = BinarySearchLt(Maps, MapEnd);
+
+ if (Map != null && Map.End > (ulong)Position)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void AddMap(
+ long Position,
+ long Size,
+ long PhysicalAddress,
+ bool VaAllocated)
+ {
+ Maps.Add(Position, new MappedMemory(Position, Size, PhysicalAddress, VaAllocated));
+ }
+
+ public bool RemoveMap(long Position, out long Size)
+ {
+ Size = 0;
+
+ if (Maps.Remove(Position, out Range Value))
+ {
+ MappedMemory Map = (MappedMemory)Value;
+
+ if (Map.VaAllocated)
+ {
+ Size = (long)(Map.End - Map.Start);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool TryGetMapPhysicalAddress(long Position, out long PhysicalAddress)
+ {
+ Range Map = BinarySearch(Maps, Position);
+
+ if (Map != null)
+ {
+ PhysicalAddress = ((MappedMemory)Map).PhysicalAddress;
+
+ return true;
+ }
+
+ PhysicalAddress = 0;
+
+ return false;
+ }
+
+ public void AddReservation(long Position, long Size)
+ {
+ Reservations.Add(Position, new Range(Position, Size));
+ }
+
+ public bool RemoveReservation(long Position)
+ {
+ return Reservations.Remove(Position);
+ }
+
+ private Range BinarySearch(SortedList<long, Range> Lst, long Position)
+ {
+ int Left = 0;
+ int Right = Lst.Count - 1;
+
+ while (Left <= Right)
+ {
+ int Size = Right - Left;
+
+ int Middle = Left + (Size >> 1);
+
+ Range Rg = Lst.Values[Middle];
+
+ if ((ulong)Position >= Rg.Start && (ulong)Position < Rg.End)
+ {
+ return Rg;
+ }
+
+ if ((ulong)Position < Rg.Start)
+ {
+ Right = Middle - 1;
+ }
+ else
+ {
+ Left = Middle + 1;
+ }
+ }
+
+ return null;
+ }
+
+ private Range BinarySearchLt(SortedList<long, Range> Lst, long Position)
+ {
+ Range LtRg = null;
+
+ int Left = 0;
+ int Right = Lst.Count - 1;
+
+ while (Left <= Right)
+ {
+ int Size = Right - Left;
+
+ int Middle = Left + (Size >> 1);
+
+ Range Rg = Lst.Values[Middle];
+
+ if ((ulong)Position < Rg.Start)
+ {
+ Right = Middle - 1;
+ }
+ else
+ {
+ Left = Middle + 1;
+
+ LtRg = Rg;
+ }
+ }
+
+ return LtRg;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs
new file mode 100644
index 00000000..c9be18ea
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs
@@ -0,0 +1,329 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Gpu.Memory;
+using Ryujinx.HLE.HOS.Services.Nv.NvMap;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ class NvGpuASIoctl
+ {
+ private const int FlagFixedOffset = 1;
+
+ private const int FlagRemapSubRange = 0x100;
+
+ private static ConcurrentDictionary<Process, NvGpuASCtx> ASCtxs;
+
+ static NvGpuASIoctl()
+ {
+ ASCtxs = new ConcurrentDictionary<Process, NvGpuASCtx>();
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4101: return BindChannel (Context);
+ case 0x4102: return AllocSpace (Context);
+ case 0x4103: return FreeSpace (Context);
+ case 0x4105: return UnmapBuffer (Context);
+ case 0x4106: return MapBufferEx (Context);
+ case 0x4108: return GetVaRegions(Context);
+ case 0x4109: return InitializeEx(Context);
+ case 0x4114: return Remap (Context, Cmd);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int BindChannel(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int AllocSpace(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
+
+ NvGpuASCtx ASCtx = GetASCtx(Context);
+
+ ulong Size = (ulong)Args.Pages *
+ (ulong)Args.PageSize;
+
+ int Result = NvResult.Success;
+
+ lock (ASCtx)
+ {
+ //Note: When the fixed offset flag is not set,
+ //the Offset field holds the alignment size instead.
+ if ((Args.Flags & FlagFixedOffset) != 0)
+ {
+ Args.Offset = ASCtx.Vmm.ReserveFixed(Args.Offset, (long)Size);
+ }
+ else
+ {
+ Args.Offset = ASCtx.Vmm.Reserve((long)Size, Args.Offset);
+ }
+
+ if (Args.Offset < 0)
+ {
+ Args.Offset = 0;
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {Size:x16}!");
+
+ Result = NvResult.OutOfMemory;
+ }
+ else
+ {
+ ASCtx.AddReservation(Args.Offset, (long)Size);
+ }
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int FreeSpace(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
+
+ NvGpuASCtx ASCtx = GetASCtx(Context);
+
+ int Result = NvResult.Success;
+
+ lock (ASCtx)
+ {
+ ulong Size = (ulong)Args.Pages *
+ (ulong)Args.PageSize;
+
+ if (ASCtx.RemoveReservation(Args.Offset))
+ {
+ ASCtx.Vmm.Free(Args.Offset, (long)Size);
+ }
+ else
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv,
+ $"Failed to free offset 0x{Args.Offset:x16} size 0x{Size:x16}!");
+
+ Result = NvResult.InvalidInput;
+ }
+ }
+
+ return Result;
+ }
+
+ private static int UnmapBuffer(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuASUnmapBuffer Args = AMemoryHelper.Read<NvGpuASUnmapBuffer>(Context.Memory, InputPosition);
+
+ NvGpuASCtx ASCtx = GetASCtx(Context);
+
+ lock (ASCtx)
+ {
+ if (ASCtx.RemoveMap(Args.Offset, out long Size))
+ {
+ if (Size != 0)
+ {
+ ASCtx.Vmm.Free(Args.Offset, Size);
+ }
+ }
+ else
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!");
+ }
+ }
+
+ return NvResult.Success;
+ }
+
+ private static int MapBufferEx(ServiceCtx Context)
+ {
+ const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!";
+
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuASMapBufferEx Args = AMemoryHelper.Read<NvGpuASMapBufferEx>(Context.Memory, InputPosition);
+
+ NvGpuASCtx ASCtx = GetASCtx(Context);
+
+ NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ long PA;
+
+ if ((Args.Flags & FlagRemapSubRange) != 0)
+ {
+ lock (ASCtx)
+ {
+ if (ASCtx.TryGetMapPhysicalAddress(Args.Offset, out PA))
+ {
+ long VA = Args.Offset + Args.BufferOffset;
+
+ PA += Args.BufferOffset;
+
+ if (ASCtx.Vmm.Map(PA, VA, Args.MappingSize) < 0)
+ {
+ string Msg = string.Format(MapErrorMsg, VA, Args.MappingSize);
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg);
+
+ return NvResult.InvalidInput;
+ }
+
+ return NvResult.Success;
+ }
+ else
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Address 0x{Args.Offset:x16} not mapped!");
+
+ return NvResult.InvalidInput;
+ }
+ }
+ }
+
+ PA = Map.Address + Args.BufferOffset;
+
+ long Size = Args.MappingSize;
+
+ if (Size == 0)
+ {
+ Size = (uint)Map.Size;
+ }
+
+ int Result = NvResult.Success;
+
+ lock (ASCtx)
+ {
+ //Note: When the fixed offset flag is not set,
+ //the Offset field holds the alignment size instead.
+ bool VaAllocated = (Args.Flags & FlagFixedOffset) == 0;
+
+ if (!VaAllocated)
+ {
+ if (ASCtx.ValidateFixedBuffer(Args.Offset, Size))
+ {
+ Args.Offset = ASCtx.Vmm.Map(PA, Args.Offset, Size);
+ }
+ else
+ {
+ string Msg = string.Format(MapErrorMsg, Args.Offset, Size);
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, Msg);
+
+ Result = NvResult.InvalidInput;
+ }
+ }
+ else
+ {
+ Args.Offset = ASCtx.Vmm.Map(PA, Size);
+ }
+
+ if (Args.Offset < 0)
+ {
+ Args.Offset = 0;
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{Size:x16}!");
+
+ Result = NvResult.InvalidInput;
+ }
+ else
+ {
+ ASCtx.AddMap(Args.Offset, Size, PA, VaAllocated);
+ }
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int GetVaRegions(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int InitializeEx(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int Remap(ServiceCtx Context, int Cmd)
+ {
+ int Count = ((Cmd >> 16) & 0xff) / 0x14;
+
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+
+ for (int Index = 0; Index < Count; Index++, InputPosition += 0x14)
+ {
+ NvGpuASRemap Args = AMemoryHelper.Read<NvGpuASRemap>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetASCtx(Context).Vmm;
+
+ NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ long Result = Vmm.Map(Map.Address, (long)(uint)Args.Offset << 16,
+ (long)(uint)Args.Pages << 16);
+
+ if (Result < 0)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv,
+ $"Page 0x{Args.Offset:x16} size 0x{Args.Pages:x16} not allocated!");
+
+ return NvResult.InvalidInput;
+ }
+ }
+
+ return NvResult.Success;
+ }
+
+ public static NvGpuASCtx GetASCtx(ServiceCtx Context)
+ {
+ return ASCtxs.GetOrAdd(Context.Process, (Key) => new NvGpuASCtx(Context));
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ ASCtxs.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs
new file mode 100644
index 00000000..4bdb4bf7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ struct NvGpuASMapBufferEx
+ {
+ public int Flags;
+ public int Kind;
+ public int NvMapHandle;
+ public int PageSize;
+ public long BufferOffset;
+ public long MappingSize;
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASRemap.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASRemap.cs
new file mode 100644
index 00000000..a24221ba
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASRemap.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ struct NvGpuASRemap
+ {
+ public short Flags;
+ public short Kind;
+ public int NvMapHandle;
+ public int Padding;
+ public int Offset;
+ public int Pages;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs
new file mode 100644
index 00000000..beb07fe2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS
+{
+ struct NvGpuASUnmapBuffer
+ {
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs
new file mode 100644
index 00000000..e20d21f9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetActiveSlotMask.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuGetActiveSlotMask
+ {
+ public int Slot;
+ public int Mask;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs
new file mode 100644
index 00000000..9a925138
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuGetCharacteristics
+ {
+ public long BufferSize;
+ public long BufferAddress;
+ 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;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs
new file mode 100644
index 00000000..751363c6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuGetTpcMasks.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuGetTpcMasks
+ {
+ public int MaskBufferSize;
+ public int Reserved;
+ public long MaskBufferAddress;
+ public int TpcMask;
+ public int Padding;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs
new file mode 100644
index 00000000..5ae45c1e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs
@@ -0,0 +1,187 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ class NvGpuGpuIoctl
+ {
+ private static Stopwatch PTimer;
+
+ private static double TicksToNs;
+
+ static NvGpuGpuIoctl()
+ {
+ PTimer = new Stopwatch();
+
+ PTimer.Start();
+
+ TicksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4701: return ZcullGetCtxSize (Context);
+ case 0x4702: return ZcullGetInfo (Context);
+ case 0x4703: return ZbcSetTable (Context);
+ case 0x4705: return GetCharacteristics(Context);
+ case 0x4706: return GetTpcMasks (Context);
+ case 0x4714: return GetActiveSlotMask (Context);
+ case 0x471c: return GetGpuTime (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int ZcullGetCtxSize(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuGpuZcullGetCtxSize Args = new NvGpuGpuZcullGetCtxSize();
+
+ Args.Size = 1;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZcullGetInfo(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuGpuZcullGetInfo Args = new NvGpuGpuZcullGetInfo();
+
+ Args.WidthAlignPixels = 0x20;
+ Args.HeightAlignPixels = 0x20;
+ Args.PixelSquaresByAliquots = 0x400;
+ Args.AliquotTotal = 0x800;
+ Args.RegionByteMultiplier = 0x20;
+ Args.RegionHeaderSize = 0x20;
+ Args.SubregionHeaderSize = 0xc0;
+ Args.SubregionWidthAlignPixels = 0x20;
+ Args.SubregionHeightAlignPixels = 0x40;
+ Args.SubregionCount = 0x10;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZbcSetTable(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int GetCharacteristics(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuGpuGetCharacteristics Args = AMemoryHelper.Read<NvGpuGpuGetCharacteristics>(Context.Memory, InputPosition);
+
+ Args.BufferSize = 0xa0;
+
+ Args.Arch = 0x120;
+ Args.Impl = 0xb;
+ Args.Rev = 0xa1;
+ Args.NumGpc = 0x1;
+ Args.L2CacheSize = 0x40000;
+ Args.OnBoardVideoMemorySize = 0x0;
+ Args.NumTpcPerGpc = 0x2;
+ Args.BusType = 0x20;
+ Args.BigPageSize = 0x20000;
+ Args.CompressionPageSize = 0x20000;
+ Args.PdeCoverageBitCount = 0x1b;
+ Args.AvailableBigPageSizes = 0x30000;
+ Args.GpcMask = 0x1;
+ Args.SmArchSmVersion = 0x503;
+ Args.SmArchSpaVersion = 0x503;
+ Args.SmArchWarpCount = 0x80;
+ Args.GpuVaBitCount = 0x28;
+ Args.Reserved = 0x0;
+ Args.Flags = 0x55;
+ Args.TwodClass = 0x902d;
+ Args.ThreedClass = 0xb197;
+ Args.ComputeClass = 0xb1c0;
+ Args.GpfifoClass = 0xb06f;
+ Args.InlineToMemoryClass = 0xa140;
+ Args.DmaCopyClass = 0xb0b5;
+ Args.MaxFbpsCount = 0x1;
+ Args.FbpEnMask = 0x0;
+ Args.MaxLtcPerFbp = 0x2;
+ Args.MaxLtsPerLtc = 0x1;
+ Args.MaxTexPerTpc = 0x0;
+ Args.MaxGpcCount = 0x1;
+ Args.RopL2EnMask0 = 0x21d70;
+ Args.RopL2EnMask1 = 0x0;
+ Args.ChipName = 0x6230326d67;
+ Args.GrCompbitStoreBaseHw = 0x0;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int GetTpcMasks(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuGpuGetTpcMasks Args = AMemoryHelper.Read<NvGpuGpuGetTpcMasks>(Context.Memory, InputPosition);
+
+ if (Args.MaskBufferSize != 0)
+ {
+ Args.TpcMask = 3;
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int GetActiveSlotMask(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvGpuGpuGetActiveSlotMask Args = new NvGpuGpuGetActiveSlotMask();
+
+ Args.Slot = 0x07;
+ Args.Mask = 0x01;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int GetGpuTime(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Memory.WriteInt64(OutputPosition, GetPTimerNanoSeconds());
+
+ return NvResult.Success;
+ }
+
+ private static long GetPTimerNanoSeconds()
+ {
+ double Ticks = PTimer.ElapsedTicks;
+
+ return (long)(Ticks * TicksToNs) & 0xff_ffff_ffff_ffff;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs
new file mode 100644
index 00000000..3ac9f92b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetCtxSize.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuZcullGetCtxSize
+ {
+ public int Size;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs
new file mode 100644
index 00000000..4a7cafd5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuGpu/NvGpuGpuZcullGetInfo.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuZcullGetInfo
+ {
+ 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/Ryujinx.HLE/HOS/Services/Nv/NvHelper.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHelper.cs
new file mode 100644
index 00000000..d9a1c452
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHelper.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Nv
+{
+ static class NvHelper
+ {
+ public static void Crash()
+ {
+
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannel.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannel.cs
new file mode 100644
index 00000000..54e709f9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannel.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel
+{
+ class NvChannel
+ {
+ public int Timeout;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannelName.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannelName.cs
new file mode 100644
index 00000000..a2b5ea4c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvChannelName.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel
+{
+ enum NvChannelName
+ {
+ Gpu
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs
new file mode 100644
index 00000000..842447f3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs
@@ -0,0 +1,210 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Gpu.Memory;
+using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel
+{
+ class NvHostChannelIoctl
+ {
+ private class ChannelsPerProcess
+ {
+ public ConcurrentDictionary<NvChannelName, NvChannel> Channels { get; private set; }
+
+ public ChannelsPerProcess()
+ {
+ Channels = new ConcurrentDictionary<NvChannelName, NvChannel>();
+
+ Channels.TryAdd(NvChannelName.Gpu, new NvChannel());
+ }
+ }
+
+ private static ConcurrentDictionary<Process, ChannelsPerProcess> Channels;
+
+ static NvHostChannelIoctl()
+ {
+ Channels = new ConcurrentDictionary<Process, ChannelsPerProcess>();
+ }
+
+ public static int ProcessIoctlGpu(ServiceCtx Context, int Cmd)
+ {
+ return ProcessIoctl(Context, NvChannelName.Gpu, Cmd);
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, NvChannelName Channel, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4714: return SetUserData (Context);
+ case 0x4801: return SetNvMap (Context);
+ case 0x4803: return SetTimeout (Context, Channel);
+ case 0x4808: return SubmitGpfifo (Context);
+ case 0x4809: return AllocObjCtx (Context);
+ case 0x480b: return ZcullBind (Context);
+ case 0x480c: return SetErrorNotifier (Context);
+ case 0x480d: return SetPriority (Context);
+ case 0x481a: return AllocGpfifoEx2 (Context);
+ case 0x481b: return KickoffPbWithAttr(Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int SetUserData(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetNvMap(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetTimeout(ServiceCtx Context, NvChannelName Channel)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+
+ GetChannel(Context, Channel).Timeout = Context.Memory.ReadInt32(InputPosition);
+
+ return NvResult.Success;
+ }
+
+ private static int SubmitGpfifo(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read<NvHostChannelSubmitGpfifo>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = NvGpuASIoctl.GetASCtx(Context).Vmm;;
+
+ for (int Index = 0; Index < Args.NumEntries; Index++)
+ {
+ long Gpfifo = Context.Memory.ReadInt64(InputPosition + 0x18 + Index * 8);
+
+ PushGpfifo(Context, Vmm, Gpfifo);
+ }
+
+ Args.SyncptId = 0;
+ Args.SyncptValue = 0;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int AllocObjCtx(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZcullBind(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetErrorNotifier(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetPriority(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int AllocGpfifoEx2(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int KickoffPbWithAttr(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read<NvHostChannelSubmitGpfifo>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = NvGpuASIoctl.GetASCtx(Context).Vmm;;
+
+ for (int Index = 0; Index < Args.NumEntries; Index++)
+ {
+ long Gpfifo = Context.Memory.ReadInt64(Args.Address + Index * 8);
+
+ PushGpfifo(Context, Vmm, Gpfifo);
+ }
+
+ Args.SyncptId = 0;
+ Args.SyncptValue = 0;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static void PushGpfifo(ServiceCtx Context, NvGpuVmm Vmm, long Gpfifo)
+ {
+ long VA = Gpfifo & 0xff_ffff_ffff;
+
+ int Size = (int)(Gpfifo >> 40) & 0x7ffffc;
+
+ byte[] Data = Vmm.ReadBytes(VA, Size);
+
+ NvGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data);
+
+ Context.Device.Gpu.Fifo.PushBuffer(Vmm, PushBuffer);
+ }
+
+ public static NvChannel GetChannel(ServiceCtx Context, NvChannelName Channel)
+ {
+ ChannelsPerProcess Cpp = Channels.GetOrAdd(Context.Process, (Key) =>
+ {
+ return new ChannelsPerProcess();
+ });
+
+ return Cpp.Channels[Channel];
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ Channels.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs
new file mode 100644
index 00000000..edebcfeb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel
+{
+ struct NvHostChannelSubmitGpfifo
+ {
+ public long Address;
+ public int NumEntries;
+ public int Flags;
+ public int SyncptId;
+ public int SyncptValue;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs
new file mode 100644
index 00000000..2bfe8882
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs
@@ -0,0 +1,398 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ class NvHostCtrlIoctl
+ {
+ private static ConcurrentDictionary<Process, NvHostCtrlUserCtx> UserCtxs;
+
+ private static bool IsProductionMode = true;
+
+ static NvHostCtrlIoctl()
+ {
+ UserCtxs = new ConcurrentDictionary<Process, NvHostCtrlUserCtx>();
+
+ if (Set.NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object ProductionModeSetting))
+ {
+ IsProductionMode = ((string)ProductionModeSetting) != "0"; // Default value is ""
+ }
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x0014: return SyncptRead (Context);
+ case 0x0015: return SyncptIncr (Context);
+ case 0x0016: return SyncptWait (Context);
+ case 0x0019: return SyncptWaitEx (Context);
+ case 0x001a: return SyncptReadMax (Context);
+ case 0x001b: return GetConfig (Context);
+ case 0x001d: return EventWait (Context);
+ case 0x001e: return EventWaitAsync(Context);
+ case 0x001f: return EventRegister (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int SyncptRead(ServiceCtx Context)
+ {
+ return SyncptReadMinOrMax(Context, Max: false);
+ }
+
+ private static int SyncptIncr(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+
+ int Id = Context.Memory.ReadInt32(InputPosition);
+
+ if ((uint)Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ GetUserCtx(Context).Syncpt.Increment(Id);
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptWait(ServiceCtx Context)
+ {
+ return SyncptWait(Context, Extended: false);
+ }
+
+ private static int SyncptWaitEx(ServiceCtx Context)
+ {
+ return SyncptWait(Context, Extended: true);
+ }
+
+ private static int SyncptReadMax(ServiceCtx Context)
+ {
+ return SyncptReadMinOrMax(Context, Max: true);
+ }
+
+ private static int GetConfig(ServiceCtx Context)
+ {
+ if (!IsProductionMode)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ string Domain = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0, 0x41);
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0x41, 0x41);
+
+ if (Set.NxSettings.Settings.TryGetValue($"{Domain}!{Name}", out object NvSetting))
+ {
+ byte[] SettingBuffer = new byte[0x101];
+
+ if (NvSetting is string StringValue)
+ {
+ if (StringValue.Length > 0x100)
+ {
+ Context.Device.Log.PrintError(Logging.LogClass.ServiceNv, $"{Domain}!{Name} String value size is too big!");
+ }
+ else
+ {
+ SettingBuffer = Encoding.ASCII.GetBytes(StringValue + "\0");
+ }
+ }
+
+ 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);
+ }
+
+ Context.Memory.WriteBytes(OutputPosition + 0x82, SettingBuffer);
+
+ Context.Device.Log.PrintDebug(Logging.LogClass.ServiceNv, $"Got setting {Domain}!{Name}");
+ }
+
+ return NvResult.Success;
+ }
+
+ return NvResult.NotAvailableInProduction;
+ }
+
+ private static int EventWait(ServiceCtx Context)
+ {
+ return EventWait(Context, Async: false);
+ }
+
+ private static int EventWaitAsync(ServiceCtx Context)
+ {
+ return EventWait(Context, Async: true);
+ }
+
+ private static int EventRegister(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ int EventId = Context.Memory.ReadInt32(InputPosition);
+
+ Context.Device.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptReadMinOrMax(ServiceCtx Context, bool Max)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvHostCtrlSyncptRead Args = AMemoryHelper.Read<NvHostCtrlSyncptRead>(Context.Memory, InputPosition);
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ if (Max)
+ {
+ Args.Value = GetUserCtx(Context).Syncpt.GetMax(Args.Id);
+ }
+ else
+ {
+ Args.Value = GetUserCtx(Context).Syncpt.GetMin(Args.Id);
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptWait(ServiceCtx Context, bool Extended)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvHostCtrlSyncptWait Args = AMemoryHelper.Read<NvHostCtrlSyncptWait>(Context.Memory, InputPosition);
+
+ NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt;
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ int Result;
+
+ if (Syncpt.MinCompare(Args.Id, Args.Thresh))
+ {
+ Result = NvResult.Success;
+ }
+ else if (Args.Timeout == 0)
+ {
+ Result = NvResult.TryAgain;
+ }
+ else
+ {
+ Context.Device.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms...");
+
+ using (ManualResetEvent WaitEvent = new ManualResetEvent(false))
+ {
+ Syncpt.AddWaiter(Args.Thresh, WaitEvent);
+
+ //Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
+ //in this case we just use the maximum timeout possible.
+ int Timeout = Args.Timeout;
+
+ if (Timeout < -1)
+ {
+ Timeout = int.MaxValue;
+ }
+
+ if (Timeout == -1)
+ {
+ WaitEvent.WaitOne();
+
+ Result = NvResult.Success;
+ }
+ else if (WaitEvent.WaitOne(Timeout))
+ {
+ Result = NvResult.Success;
+ }
+ else
+ {
+ Result = NvResult.TimedOut;
+ }
+ }
+
+ Context.Device.Log.PrintDebug(LogClass.ServiceNv, "Resuming...");
+ }
+
+ if (Extended)
+ {
+ Context.Memory.WriteInt32(OutputPosition + 0xc, Syncpt.GetMin(Args.Id));
+ }
+
+ return Result;
+ }
+
+ private static int EventWait(ServiceCtx Context, bool Async)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvHostCtrlSyncptWaitEx Args = AMemoryHelper.Read<NvHostCtrlSyncptWaitEx>(Context.Memory, InputPosition);
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ void WriteArgs()
+ {
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+ }
+
+ NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt;
+
+ if (Syncpt.MinCompare(Args.Id, Args.Thresh))
+ {
+ Args.Value = Syncpt.GetMin(Args.Id);
+
+ WriteArgs();
+
+ return NvResult.Success;
+ }
+
+ if (!Async)
+ {
+ Args.Value = 0;
+ }
+
+ if (Args.Timeout == 0)
+ {
+ WriteArgs();
+
+ return NvResult.TryAgain;
+ }
+
+ NvHostEvent Event;
+
+ int Result, EventIndex;
+
+ if (Async)
+ {
+ EventIndex = Args.Value;
+
+ if ((uint)EventIndex >= NvHostCtrlUserCtx.EventsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ Event = GetUserCtx(Context).Events[EventIndex];
+ }
+ else
+ {
+ Event = GetFreeEvent(Context, Syncpt, Args.Id, out EventIndex);
+ }
+
+ if (Event != null &&
+ (Event.State == NvHostEventState.Registered ||
+ Event.State == NvHostEventState.Free))
+ {
+ Event.Id = Args.Id;
+ Event.Thresh = Args.Thresh;
+
+ Event.State = NvHostEventState.Waiting;
+
+ if (!Async)
+ {
+ Args.Value = ((Args.Id & 0xfff) << 16) | 0x10000000;
+ }
+ else
+ {
+ Args.Value = Args.Id << 4;
+ }
+
+ Args.Value |= EventIndex;
+
+ Result = NvResult.TryAgain;
+ }
+ else
+ {
+ Result = NvResult.InvalidInput;
+ }
+
+ WriteArgs();
+
+ return Result;
+ }
+
+ private static NvHostEvent GetFreeEvent(
+ ServiceCtx Context,
+ NvHostSyncpt Syncpt,
+ int Id,
+ out int EventIndex)
+ {
+ NvHostEvent[] Events = GetUserCtx(Context).Events;
+
+ EventIndex = NvHostCtrlUserCtx.EventsCount;
+
+ int NullIndex = NvHostCtrlUserCtx.EventsCount;
+
+ for (int Index = 0; Index < NvHostCtrlUserCtx.EventsCount; Index++)
+ {
+ NvHostEvent Event = Events[Index];
+
+ if (Event != null)
+ {
+ if (Event.State == NvHostEventState.Registered ||
+ Event.State == NvHostEventState.Free)
+ {
+ EventIndex = Index;
+
+ if (Event.Id == Id)
+ {
+ return Event;
+ }
+ }
+ }
+ else if (NullIndex == NvHostCtrlUserCtx.EventsCount)
+ {
+ NullIndex = Index;
+ }
+ }
+
+ if (NullIndex < NvHostCtrlUserCtx.EventsCount)
+ {
+ EventIndex = NullIndex;
+
+ return Events[NullIndex] = new NvHostEvent();
+ }
+
+ if (EventIndex < NvHostCtrlUserCtx.EventsCount)
+ {
+ return Events[EventIndex];
+ }
+
+ return null;
+ }
+
+ public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx Context)
+ {
+ return UserCtxs.GetOrAdd(Context.Process, (Key) => new NvHostCtrlUserCtx());
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ UserCtxs.TryRemove(Process, out _);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs
new file mode 100644
index 00000000..275b6132
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptRead
+ {
+ public int Id;
+ public int Value;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs
new file mode 100644
index 00000000..96e2f968
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptWait
+ {
+ public int Id;
+ public int Thresh;
+ public int Timeout;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs
new file mode 100644
index 00000000..6aaa4718
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptWaitEx
+ {
+ public int Id;
+ public int Thresh;
+ public int Timeout;
+ public int Value;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs
new file mode 100644
index 00000000..fcb80836
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ class NvHostCtrlUserCtx
+ {
+ public const int LocksCount = 16;
+ public const int EventsCount = 64;
+
+ public NvHostSyncpt Syncpt { get; private set; }
+
+ public NvHostEvent[] Events { get; private set; }
+
+ public NvHostCtrlUserCtx()
+ {
+ Syncpt = new NvHostSyncpt();
+
+ Events = new NvHostEvent[EventsCount];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEvent.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEvent.cs
new file mode 100644
index 00000000..71e5f1e6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEvent.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ class NvHostEvent
+ {
+ public int Id;
+ public int Thresh;
+
+ public NvHostEventState State;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEventState.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEventState.cs
new file mode 100644
index 00000000..61870c7f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostEventState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ enum NvHostEventState
+ {
+ Registered = 0,
+ Waiting = 1,
+ Busy = 2,
+ Free = 5
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostSyncPt.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostSyncPt.cs
new file mode 100644
index 00000000..9ffa93f2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostSyncPt.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl
+{
+ class NvHostSyncpt
+ {
+ public const int SyncptsCount = 192;
+
+ private int[] CounterMin;
+ private int[] CounterMax;
+
+ private long EventMask;
+
+ private ConcurrentDictionary<EventWaitHandle, int> Waiters;
+
+ public NvHostSyncpt()
+ {
+ CounterMin = new int[SyncptsCount];
+ CounterMax = new int[SyncptsCount];
+
+ Waiters = new ConcurrentDictionary<EventWaitHandle, int>();
+ }
+
+ public int GetMin(int Id)
+ {
+ return CounterMin[Id];
+ }
+
+ public int GetMax(int Id)
+ {
+ return CounterMax[Id];
+ }
+
+ public int Increment(int Id)
+ {
+ if (((EventMask >> Id) & 1) != 0)
+ {
+ Interlocked.Increment(ref CounterMax[Id]);
+ }
+
+ return IncrementMin(Id);
+ }
+
+ public int IncrementMin(int Id)
+ {
+ int Value = Interlocked.Increment(ref CounterMin[Id]);
+
+ WakeUpWaiters(Id, Value);
+
+ return Value;
+ }
+
+ public int IncrementMax(int Id)
+ {
+ return Interlocked.Increment(ref CounterMax[Id]);
+ }
+
+ public void AddWaiter(int Threshold, EventWaitHandle WaitEvent)
+ {
+ if (!Waiters.TryAdd(WaitEvent, Threshold))
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ public bool RemoveWaiter(EventWaitHandle WaitEvent)
+ {
+ return Waiters.TryRemove(WaitEvent, out _);
+ }
+
+ private void WakeUpWaiters(int Id, int NewValue)
+ {
+ foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters)
+ {
+ if (MinCompare(Id, NewValue, CounterMax[Id], KV.Value))
+ {
+ KV.Key.Set();
+
+ Waiters.TryRemove(KV.Key, out _);
+ }
+ }
+ }
+
+ public bool MinCompare(int Id, int Threshold)
+ {
+ return MinCompare(Id, CounterMin[Id], CounterMax[Id], Threshold);
+ }
+
+ private bool MinCompare(int Id, int Min, int Max, int Threshold)
+ {
+ int MinDiff = Min - Threshold;
+ int MaxDiff = Max - Threshold;
+
+ if (((EventMask >> Id) & 1) != 0)
+ {
+ return MinDiff >= 0;
+ }
+ else
+ {
+ return (uint)MaxDiff >= (uint)MinDiff;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapAlloc.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapAlloc.cs
new file mode 100644
index 00000000..bc61baad
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapAlloc.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapAlloc
+ {
+ public int Handle;
+ public int HeapMask;
+ public int Flags;
+ public int Align;
+ public long Kind;
+ public long Address;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapCreate.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapCreate.cs
new file mode 100644
index 00000000..dd4bff98
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapCreate.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapCreate
+ {
+ public int Size;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFree.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFree.cs
new file mode 100644
index 00000000..d946987e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFree.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapFree
+ {
+ public int Handle;
+ public int Padding;
+ public long Address;
+ public int Size;
+ public int Flags;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFromId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFromId.cs
new file mode 100644
index 00000000..e49257ea
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapFromId.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapFromId
+ {
+ public int Id;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapGetId.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapGetId.cs
new file mode 100644
index 00000000..1f4f3290
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapGetId.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapGetId
+ {
+ public int Id;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandle.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandle.cs
new file mode 100644
index 00000000..3f8a1517
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandle.cs
@@ -0,0 +1,37 @@
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ class NvMapHandle
+ {
+ public int Handle;
+ public int Id;
+ public int Size;
+ public int Align;
+ public int Kind;
+ public long Address;
+ public bool Allocated;
+
+ private long Dupes;
+
+ public NvMapHandle()
+ {
+ Dupes = 1;
+ }
+
+ public NvMapHandle(int Size) : this()
+ {
+ 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/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandleParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandleParam.cs
new file mode 100644
index 00000000..b360343c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapHandleParam.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ enum NvMapHandleParam
+ {
+ Size = 1,
+ Align = 2,
+ Base = 3,
+ Heap = 4,
+ Kind = 5,
+ Compr = 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs
new file mode 100644
index 00000000..782f7b80
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs
@@ -0,0 +1,302 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.Gpu.Memory;
+using Ryujinx.HLE.Logging;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ class NvMapIoctl
+ {
+ private const int FlagNotFreedYet = 1;
+
+ private static ConcurrentDictionary<Process, IdDictionary> Maps;
+
+ static NvMapIoctl()
+ {
+ Maps = new ConcurrentDictionary<Process, IdDictionary>();
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x0101: return Create(Context);
+ case 0x0103: return FromId(Context);
+ case 0x0104: return Alloc (Context);
+ case 0x0105: return Free (Context);
+ case 0x0109: return Param (Context);
+ case 0x010e: return GetId (Context);
+ }
+
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!");
+
+ return NvResult.NotSupported;
+ }
+
+ private static int Create(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapCreate Args = AMemoryHelper.Read<NvMapCreate>(Context.Memory, InputPosition);
+
+ if (Args.Size == 0)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ int Size = IntUtils.AlignUp(Args.Size, NvGpuVmm.PageSize);
+
+ Args.Handle = AddNvMap(Context, new NvMapHandle(Size));
+
+ Context.Device.Log.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!");
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int FromId(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapFromId Args = AMemoryHelper.Read<NvMapFromId>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Id);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ Map.IncrementRefCount();
+
+ Args.Handle = Args.Id;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int Alloc(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapAlloc Args = AMemoryHelper.Read<NvMapAlloc>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if ((Args.Align & (Args.Align - 1)) != 0)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if ((uint)Args.Align < NvGpuVmm.PageSize)
+ {
+ Args.Align = NvGpuVmm.PageSize;
+ }
+
+ int Result = NvResult.Success;
+
+ if (!Map.Allocated)
+ {
+ Map.Allocated = true;
+
+ Map.Align = Args.Align;
+ Map.Kind = (byte)Args.Kind;
+
+ int Size = IntUtils.AlignUp(Map.Size, NvGpuVmm.PageSize);
+
+ long Address = Args.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?
+ if (!Context.Device.Memory.Allocator.TryAllocate((uint)Size, out Address))
+ {
+ Result = NvResult.OutOfMemory;
+ }
+ }
+
+ if (Result == NvResult.Success)
+ {
+ Map.Size = Size;
+ Map.Address = Address;
+ }
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int Free(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapFree Args = AMemoryHelper.Read<NvMapFree>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if (Map.DecrementRefCount() <= 0)
+ {
+ DeleteNvMap(Context, Args.Handle);
+
+ Context.Device.Log.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!");
+
+ Args.Address = Map.Address;
+ Args.Flags = 0;
+ }
+ else
+ {
+ Args.Address = 0;
+ Args.Flags = FlagNotFreedYet;
+ }
+
+ Args.Size = Map.Size;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int Param(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapParam Args = AMemoryHelper.Read<NvMapParam>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ switch ((NvMapHandleParam)Args.Param)
+ {
+ case NvMapHandleParam.Size: Args.Result = Map.Size; break;
+ case NvMapHandleParam.Align: Args.Result = Map.Align; break;
+ case NvMapHandleParam.Heap: Args.Result = 0x40000000; break;
+ case NvMapHandleParam.Kind: Args.Result = Map.Kind; break;
+ case NvMapHandleParam.Compr: Args.Result = 0; break;
+
+ //Note: Base is not supported and returns an error.
+ //Any other value also returns an error.
+ default: return NvResult.InvalidInput;
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int GetId(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21().Position;
+ long OutputPosition = Context.Request.GetBufferType0x22().Position;
+
+ NvMapGetId Args = AMemoryHelper.Read<NvMapGetId>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ Args.Id = Args.Handle;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int AddNvMap(ServiceCtx Context, NvMapHandle Map)
+ {
+ IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>
+ {
+ IdDictionary NewDict = new IdDictionary();
+
+ NewDict.Add(0, new NvMapHandle());
+
+ return NewDict;
+ });
+
+ return Dict.Add(Map);
+ }
+
+ private static bool DeleteNvMap(ServiceCtx Context, int Handle)
+ {
+ if (Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.Delete(Handle) != null;
+ }
+
+ return false;
+ }
+
+ public static void InitializeNvMap(ServiceCtx Context)
+ {
+ IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>new IdDictionary());
+
+ Dict.Add(0, new NvMapHandle());
+ }
+
+ public static NvMapHandle GetNvMapWithFb(ServiceCtx Context, int Handle)
+ {
+ if (Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.GetData<NvMapHandle>(Handle);
+ }
+
+ return null;
+ }
+
+ public static NvMapHandle GetNvMap(ServiceCtx Context, int Handle)
+ {
+ if (Handle != 0 && Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.GetData<NvMapHandle>(Handle);
+ }
+
+ return null;
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ Maps.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapParam.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapParam.cs
new file mode 100644
index 00000000..b5213efe
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapParam.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Nv.NvMap
+{
+ struct NvMapParam
+ {
+ public int Handle;
+ public int Param;
+ public int Result;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvResult.cs b/Ryujinx.HLE/HOS/Services/Nv/NvResult.cs
new file mode 100644
index 00000000..86ecd975
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nv/NvResult.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Services.Nv
+{
+ static class NvResult
+ {
+ public const int NotAvailableInProduction = 196614;
+ public const int Success = 0;
+ public const int TryAgain = -11;
+ public const int OutOfMemory = -12;
+ public const int InvalidInput = -22;
+ public const int NotSupported = -25;
+ public const int Restart = -85;
+ public const int TimedOut = -110;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs
new file mode 100644
index 00000000..d484d312
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlService.cs
@@ -0,0 +1,41 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Pctl
+{
+ class IParentalControlService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private bool Initialized = false;
+
+ private bool NeedInitialize;
+
+ public IParentalControlService(bool NeedInitialize = true)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, Initialize }
+ };
+
+ this.NeedInitialize = NeedInitialize;
+ }
+
+ public long Initialize(ServiceCtx Context)
+ {
+ if (NeedInitialize && !Initialized)
+ {
+ Initialized = true;
+ }
+ else
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServicePctl, "Service is already initialized!");
+ }
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs
new file mode 100644
index 00000000..157d594d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Pctl
+{
+ class IParentalControlServiceFactory : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IParentalControlServiceFactory()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, CreateService },
+ { 1, CreateServiceWithoutInitialize }
+ };
+ }
+
+ public long CreateService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IParentalControlService());
+
+ return 0;
+ }
+
+ public long CreateServiceWithoutInitialize(ServiceCtx Context)
+ {
+ MakeObject(Context, new IParentalControlService(false));
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs
new file mode 100644
index 00000000..92821217
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs
@@ -0,0 +1,125 @@
+using Ryujinx.HLE.HOS.Font;
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Pl
+{
+ class ISharedFontManager : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISharedFontManager()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, RequestLoad },
+ { 1, GetLoadState },
+ { 2, GetFontSize },
+ { 3, GetSharedMemoryAddressOffset },
+ { 4, GetSharedMemoryNativeHandle },
+ { 5, GetSharedFontInOrderOfPriority }
+ };
+ }
+
+ public long 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 0;
+ }
+
+ public long 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 0;
+ }
+
+ public long GetFontSize(ServiceCtx Context)
+ {
+ SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write(Context.Device.System.Font.GetFontSize(FontType));
+
+ return 0;
+ }
+
+ public long GetSharedMemoryAddressOffset(ServiceCtx Context)
+ {
+ SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write(Context.Device.System.Font.GetSharedMemoryAddressOffset(FontType));
+
+ return 0;
+ }
+
+ public long GetSharedMemoryNativeHandle(ServiceCtx Context)
+ {
+ Context.Device.System.Font.EnsureInitialized();
+
+ int Handle = Context.Process.HandleTable.OpenHandle(Context.Device.System.FontSharedMem);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ public long GetSharedFontInOrderOfPriority(ServiceCtx Context)
+ {
+ long LanguageCode = Context.RequestData.ReadInt64();
+ int LoadedCount = 0;
+
+ for (SharedFontType Type = 0; Type < SharedFontType.Count; Type++)
+ {
+ int Offset = (int)Type * 4;
+
+ if (!AddFontToOrderOfPriorityList(Context, (SharedFontType)Type, Offset))
+ {
+ break;
+ }
+
+ LoadedCount++;
+ }
+
+ Context.ResponseData.Write(LoadedCount);
+ Context.ResponseData.Write((int)SharedFontType.Count);
+
+ return 0;
+ }
+
+ private bool AddFontToOrderOfPriorityList(ServiceCtx Context, SharedFontType FontType, int Offset)
+ {
+ long TypesPosition = Context.Request.ReceiveBuff[0].Position;
+ long TypesSize = Context.Request.ReceiveBuff[0].Size;
+
+ long OffsetsPosition = Context.Request.ReceiveBuff[1].Position;
+ long OffsetsSize = Context.Request.ReceiveBuff[1].Size;
+
+ long FontSizeBufferPosition = Context.Request.ReceiveBuff[2].Position;
+ long FontSizeBufferSize = Context.Request.ReceiveBuff[2].Size;
+
+ if ((uint)Offset + 4 > (uint)TypesSize ||
+ (uint)Offset + 4 > (uint)OffsetsSize ||
+ (uint)Offset + 4 > (uint)FontSizeBufferSize)
+ {
+ return false;
+ }
+
+ Context.Memory.WriteInt32(TypesPosition + Offset, (int)FontType);
+
+ Context.Memory.WriteInt32(OffsetsPosition + Offset, Context.Device.System.Font.GetSharedMemoryAddressOffset(FontType));
+
+ Context.Memory.WriteInt32(FontSizeBufferPosition + Offset, Context.Device.System.Font.GetFontSize(FontType));
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs
new file mode 100644
index 00000000..fa39854d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs
@@ -0,0 +1,28 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Prepo
+{
+ class IPrepoService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IPrepoService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 10101, SaveReportWithUser }
+ };
+ }
+
+ public static long SaveReportWithUser(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServicePrepo, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
new file mode 100644
index 00000000..babceb7d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
@@ -0,0 +1,181 @@
+using Ryujinx.HLE.HOS.Services.Acc;
+using Ryujinx.HLE.HOS.Services.Am;
+using Ryujinx.HLE.HOS.Services.Apm;
+using Ryujinx.HLE.HOS.Services.Aud;
+using Ryujinx.HLE.HOS.Services.Bsd;
+using Ryujinx.HLE.HOS.Services.Caps;
+using Ryujinx.HLE.HOS.Services.FspSrv;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Services.Lm;
+using Ryujinx.HLE.HOS.Services.Mm;
+using Ryujinx.HLE.HOS.Services.Nfp;
+using Ryujinx.HLE.HOS.Services.Ns;
+using Ryujinx.HLE.HOS.Services.Nv;
+using Ryujinx.HLE.HOS.Services.Pctl;
+using Ryujinx.HLE.HOS.Services.Pl;
+using Ryujinx.HLE.HOS.Services.Prepo;
+using Ryujinx.HLE.HOS.Services.Set;
+using Ryujinx.HLE.HOS.Services.Sfdnsres;
+using Ryujinx.HLE.HOS.Services.Sm;
+using Ryujinx.HLE.HOS.Services.Spl;
+using Ryujinx.HLE.HOS.Services.Ssl;
+using Ryujinx.HLE.HOS.Services.Vi;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services
+{
+ static class ServiceFactory
+ {
+ public static IpcService MakeService(string Name)
+ {
+ switch (Name)
+ {
+ case "acc:u0":
+ return new IAccountServiceForApplication();
+
+ case "aoc:u":
+ return new IAddOnContentManager();
+
+ case "apm":
+ return new IManager();
+
+ case "apm:p":
+ return new IManager();
+
+ case "appletAE":
+ return new IAllSystemAppletProxiesService();
+
+ case "appletOE":
+ return new IApplicationProxyService();
+
+ case "audout:u":
+ return new IAudioOutManager();
+
+ case "audren:u":
+ return new IAudioRendererManager();
+
+ case "bcat:a":
+ return new Bcat.IServiceCreator();
+
+ case "bcat:m":
+ return new Bcat.IServiceCreator();
+
+ case "bcat:u":
+ return new Bcat.IServiceCreator();
+
+ case "bcat:s":
+ return new Bcat.IServiceCreator();
+
+ case "bsd:s":
+ return new IClient();
+
+ case "bsd:u":
+ return new IClient();
+
+ case "caps:a":
+ return new IAlbumAccessorService();
+
+ case "caps:ss":
+ return new IScreenshotService();
+
+ case "csrng":
+ return new IRandomInterface();
+
+ case "friend:a":
+ return new Friend.IServiceCreator();
+
+ case "friend:u":
+ return new Friend.IServiceCreator();
+
+ case "fsp-srv":
+ return new IFileSystemProxy();
+
+ case "hid":
+ return new IHidServer();
+
+ case "lm":
+ return new ILogService();
+
+ case "mm:u":
+ return new IRequest();
+
+ case "nfp:user":
+ return new IUserManager();
+
+ case "nifm:u":
+ return new Nifm.IStaticService();
+
+ case "ns:ec":
+ return new IServiceGetterInterface();
+
+ case "ns:su":
+ return new ISystemUpdateInterface();
+
+ case "ns:vm":
+ return new IVulnerabilityManagerInterface();
+
+ case "nvdrv":
+ return new INvDrvServices();
+
+ case "nvdrv:a":
+ return new INvDrvServices();
+
+ case "pctl:s":
+ return new IParentalControlServiceFactory();
+
+ case "pctl:r":
+ return new IParentalControlServiceFactory();
+
+ case "pctl:a":
+ return new IParentalControlServiceFactory();
+
+ case "pctl":
+ return new IParentalControlServiceFactory();
+
+ case "pl:u":
+ return new ISharedFontManager();
+
+ case "prepo:a":
+ return new IPrepoService();
+
+ case "prepo:u":
+ return new IPrepoService();
+
+ case "set":
+ return new ISettingsServer();
+
+ case "set:sys":
+ return new ISystemSettingsServer();
+
+ case "sfdnsres":
+ return new IResolver();
+
+ case "sm:":
+ return new IUserInterface();
+
+ case "ssl":
+ return new ISslService();
+
+ case "time:a":
+ return new Time.IStaticService();
+
+ case "time:s":
+ return new Time.IStaticService();
+
+ case "time:u":
+ return new Time.IStaticService();
+
+ case "vi:m":
+ return new IManagerRootService();
+
+ case "vi:s":
+ return new ISystemRootService();
+
+ case "vi:u":
+ return new IApplicationRootService();
+ }
+
+ throw new NotImplementedException(Name);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Set/ISettingsServer.cs b/Ryujinx.HLE/HOS/Services/Set/ISettingsServer.cs
new file mode 100644
index 00000000..690d6de9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Set/ISettingsServer.cs
@@ -0,0 +1,79 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.SystemState;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Set
+{
+ class ISettingsServer : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISettingsServer()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetLanguageCode },
+ { 1, GetAvailableLanguageCodes },
+ { 3, GetAvailableLanguageCodeCount },
+ { 5, GetAvailableLanguageCodes2 }
+ };
+ }
+
+ public static long GetLanguageCode(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(Context.Device.System.State.DesiredLanguageCode);
+
+ return 0;
+ }
+
+ public static long GetAvailableLanguageCodes(ServiceCtx Context)
+ {
+ GetAvailableLanguagesCodesImpl(
+ Context,
+ Context.Request.RecvListBuff[0].Position,
+ Context.Request.RecvListBuff[0].Size);
+
+ return 0;
+ }
+
+ public static long GetAvailableLanguageCodeCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length);
+
+ return 0;
+ }
+
+ public static long GetAvailableLanguageCodes2(ServiceCtx Context)
+ {
+ GetAvailableLanguagesCodesImpl(
+ Context,
+ Context.Request.ReceiveBuff[0].Position,
+ Context.Request.ReceiveBuff[0].Size);
+
+ return 0;
+ }
+
+ public static long GetAvailableLanguagesCodesImpl(ServiceCtx Context, long Position, long Size)
+ {
+ int Count = (int)(Size / 8);
+
+ if (Count > SystemStateMgr.LanguageCodes.Length)
+ {
+ Count = SystemStateMgr.LanguageCodes.Length;
+ }
+
+ for (int Index = 0; Index < Count; Index++)
+ {
+ Context.Memory.WriteInt64(Position, SystemStateMgr.GetLanguageCode(Index));
+
+ Position += 8;
+ }
+
+ Context.ResponseData.Write(Count);
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs
new file mode 100644
index 00000000..dc146967
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Set/ISystemSettingsServer.cs
@@ -0,0 +1,151 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.SystemState;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Set
+{
+ class ISystemSettingsServer : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemSettingsServer()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 4, GetFirmwareVersion2 },
+ { 23, GetColorSetId },
+ { 24, SetColorSetId },
+ { 38, GetSettingsItemValue }
+ };
+ }
+
+ public static long GetFirmwareVersion2(ServiceCtx Context)
+ {
+ long ReplyPos = Context.Request.RecvListBuff[0].Position;
+ long ReplySize = Context.Request.RecvListBuff[0].Size;
+
+ 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.WriteBytes(ReplyPos, MS.ToArray());
+ }
+
+ return 0;
+ }
+
+ public static long GetColorSetId(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((int)Context.Device.System.State.ThemeColor);
+
+ return 0;
+ }
+
+ public static long SetColorSetId(ServiceCtx Context)
+ {
+ int ColorSetId = Context.RequestData.ReadInt32();
+
+ Context.Device.System.State.ThemeColor = (ColorSet)ColorSetId;
+
+ return 0;
+ }
+
+ public static long GetSettingsItemValue(ServiceCtx Context)
+ {
+ long ClassPos = Context.Request.PtrBuff[0].Position;
+ long ClassSize = Context.Request.PtrBuff[0].Size;
+
+ long NamePos = Context.Request.PtrBuff[1].Position;
+ long NameSize = Context.Request.PtrBuff[1].Size;
+
+ long ReplyPos = Context.Request.ReceiveBuff[0].Position;
+ long ReplySize = Context.Request.ReceiveBuff[0].Size;
+
+ byte[] Class = Context.Memory.ReadBytes(ClassPos, ClassSize);
+ byte[] Name = Context.Memory.ReadBytes(NamePos, NameSize);
+
+ string AskedSetting = Encoding.ASCII.GetString(Class).Trim('\0') + "!" + Encoding.ASCII.GetString(Name).Trim('\0');
+
+ NxSettings.Settings.TryGetValue(AskedSetting, out object NxSetting);
+
+ if (NxSetting != null)
+ {
+ byte[] SettingBuffer = new byte[ReplySize];
+
+ if (NxSetting is string StringValue)
+ {
+ if (StringValue.Length + 1 > ReplySize)
+ {
+ Context.Device.Log.PrintError(Logging.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.WriteBytes(ReplyPos, SettingBuffer);
+
+ Context.Device.Log.PrintDebug(Logging.LogClass.ServiceSet, $"{AskedSetting} set value: {NxSetting} as {NxSetting.GetType()}");
+ }
+ else
+ {
+ Context.Device.Log.PrintError(Logging.LogClass.ServiceSet, $"{AskedSetting} not found!");
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Set/NxSettings.cs b/Ryujinx.HLE/HOS/Services/Set/NxSettings.cs
new file mode 100644
index 00000000..7d1dd2ed
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Set/NxSettings.cs
@@ -0,0 +1,1711 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Set
+{
+ 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 },
+ { "usb!usb30_force_enabled", false },
+ { "wlan_debug!skip_wlan_boot", false },
+ };
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs b/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs
new file mode 100644
index 00000000..26dbedf4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sfdnsres/IResolver.cs
@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Sfdnsres
+{
+ class IResolver : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IResolver()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ //...
+ };
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
new file mode 100644
index 00000000..efa64ee7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
@@ -0,0 +1,69 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Sm
+{
+ class IUserInterface : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private bool IsInitialized;
+
+ public IUserInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, Initialize },
+ { 1, GetService }
+ };
+ }
+
+ private const int SmNotInitialized = 0x415;
+
+ public long Initialize(ServiceCtx Context)
+ {
+ IsInitialized = true;
+
+ return 0;
+ }
+
+ public long GetService(ServiceCtx Context)
+ {
+ //Only for kernel version > 3.0.0.
+ if (!IsInitialized)
+ {
+ //return SmNotInitialized;
+ }
+
+ 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;
+ }
+ }
+
+ if (Name == string.Empty)
+ {
+ return 0;
+ }
+
+ KSession Session = new KSession(ServiceFactory.MakeService(Name), Name);
+
+ int Handle = Context.Process.HandleTable.OpenHandle(Session);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs
new file mode 100644
index 00000000..6391d981
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs
@@ -0,0 +1,50 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+namespace Ryujinx.HLE.HOS.Services.Spl
+{
+ class IRandomInterface : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private RNGCryptoServiceProvider Rng;
+
+ public IRandomInterface()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetRandomBytes }
+ };
+
+ Rng = new RNGCryptoServiceProvider();
+ }
+
+ public long GetRandomBytes(ServiceCtx Context)
+ {
+ byte[] RandomBytes = new byte[Context.Request.ReceiveBuff[0].Size];
+
+ Rng.GetBytes(RandomBytes);
+
+ Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, RandomBytes);
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ Rng.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs
new file mode 100644
index 00000000..b59527f7
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs
@@ -0,0 +1,30 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Ssl
+{
+ class ISslService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISslService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 5, SetInterfaceVersion }
+ };
+ }
+
+ public long SetInterfaceVersion(ServiceCtx Context)
+ {
+ int Version = Context.RequestData.ReadInt32();
+
+ Context.Device.Log.PrintStub(LogClass.ServiceSsl, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
new file mode 100644
index 00000000..66f16501
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
@@ -0,0 +1,74 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class IStaticService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private static readonly DateTime StartupDate = DateTime.UtcNow;
+
+ public IStaticService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetStandardUserSystemClock },
+ { 1, GetStandardNetworkSystemClock },
+ { 2, GetStandardSteadyClock },
+ { 3, GetTimeZoneService },
+ { 4, GetStandardLocalSystemClock },
+ { 300, CalculateMonotonicSystemClockBaseTimePoint }
+ };
+ }
+
+ public long GetStandardUserSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.User));
+
+ return 0;
+ }
+
+ public long GetStandardNetworkSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.Network));
+
+ return 0;
+ }
+
+ public long GetStandardSteadyClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISteadyClock());
+
+ return 0;
+ }
+
+ public long GetTimeZoneService(ServiceCtx Context)
+ {
+ MakeObject(Context, new ITimeZoneService());
+
+ return 0;
+ }
+
+ public long GetStandardLocalSystemClock(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemClock(SystemClockType.Local));
+
+ return 0;
+ }
+
+ public long CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx Context)
+ {
+ long TimeOffset = (long)(DateTime.UtcNow - StartupDate).TotalSeconds;
+ long SystemClockContextEpoch = Context.RequestData.ReadInt64();
+
+ Context.ResponseData.Write(TimeOffset + SystemClockContextEpoch);
+
+ return 0;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs
new file mode 100644
index 00000000..e4020bb1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs
@@ -0,0 +1,53 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class ISteadyClock : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private ulong TestOffset;
+
+ public ISteadyClock()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCurrentTimePoint },
+ { 1, GetTestOffset },
+ { 2, SetTestOffset }
+ };
+
+ TestOffset = 0;
+ }
+
+ public long GetCurrentTimePoint(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)(System.Diagnostics.Process.GetCurrentProcess().StartTime - DateTime.Now).TotalSeconds);
+
+ for (int i = 0; i < 0x10; i++)
+ {
+ Context.ResponseData.Write((byte)0);
+ }
+
+ return 0;
+ }
+
+ public long GetTestOffset(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(TestOffset);
+
+ return 0;
+ }
+
+ public long SetTestOffset(ServiceCtx Context)
+ {
+ TestOffset = Context.RequestData.ReadUInt64();
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs
new file mode 100644
index 00000000..f574826a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs
@@ -0,0 +1,107 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class ISystemClock : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private SystemClockType ClockType;
+
+ private DateTime SystemClockContextEpoch;
+
+ private long SystemClockTimePoint;
+
+ private byte[] SystemClockContextEnding;
+
+ private long TimeOffset;
+
+ public ISystemClock(SystemClockType ClockType)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetCurrentTime },
+ { 1, SetCurrentTime },
+ { 2, GetSystemClockContext },
+ { 3, SetSystemClockContext }
+ };
+
+ this.ClockType = ClockType;
+ SystemClockContextEpoch = System.Diagnostics.Process.GetCurrentProcess().StartTime;
+ SystemClockContextEnding = new byte[0x10];
+ TimeOffset = 0;
+
+ if (ClockType == SystemClockType.User ||
+ ClockType == SystemClockType.Network)
+ {
+ SystemClockContextEpoch = SystemClockContextEpoch.ToUniversalTime();
+ }
+
+ SystemClockTimePoint = (long)(SystemClockContextEpoch - Epoch).TotalSeconds;
+ }
+
+ public long GetCurrentTime(ServiceCtx Context)
+ {
+ DateTime CurrentTime = DateTime.Now;
+
+ if (ClockType == SystemClockType.User ||
+ ClockType == SystemClockType.Network)
+ {
+ CurrentTime = CurrentTime.ToUniversalTime();
+ }
+
+ Context.ResponseData.Write((long)((CurrentTime - Epoch).TotalSeconds) + TimeOffset);
+
+ return 0;
+ }
+
+ public long SetCurrentTime(ServiceCtx Context)
+ {
+ DateTime CurrentTime = DateTime.Now;
+
+ if (ClockType == SystemClockType.User ||
+ ClockType == SystemClockType.Network)
+ {
+ CurrentTime = CurrentTime.ToUniversalTime();
+ }
+
+ TimeOffset = (Context.RequestData.ReadInt64() - (long)(CurrentTime - Epoch).TotalSeconds);
+
+ return 0;
+ }
+
+ public long GetSystemClockContext(ServiceCtx Context)
+ {
+ Context.ResponseData.Write((long)(SystemClockContextEpoch - Epoch).TotalSeconds);
+
+ // The point in time, TODO: is there a link between epoch and this?
+ Context.ResponseData.Write(SystemClockTimePoint);
+
+ // This seems to be some kind of identifier?
+ for (int i = 0; i < 0x10; i++)
+ {
+ Context.ResponseData.Write(SystemClockContextEnding[i]);
+ }
+
+ return 0;
+ }
+
+ public long SetSystemClockContext(ServiceCtx Context)
+ {
+ long NewSystemClockEpoch = Context.RequestData.ReadInt64();
+ long NewSystemClockTimePoint = Context.RequestData.ReadInt64();
+
+ SystemClockContextEpoch = Epoch.Add(TimeSpan.FromSeconds(NewSystemClockEpoch));
+ SystemClockTimePoint = NewSystemClockTimePoint;
+ SystemClockContextEnding = Context.RequestData.ReadBytes(0x10);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs
new file mode 100644
index 00000000..6df28659
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs
@@ -0,0 +1,280 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class ITimeZoneService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ private TimeZoneInfo TimeZone = TimeZoneInfo.Local;
+
+ public ITimeZoneService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetDeviceLocationName },
+ { 1, SetDeviceLocationName },
+ { 2, GetTotalLocationNameCount },
+ { 3, LoadLocationNameList },
+ { 4, LoadTimeZoneRule },
+ { 100, ToCalendarTime },
+ { 101, ToCalendarTimeWithMyRule },
+ { 201, ToPosixTime },
+ { 202, ToPosixTimeWithMyRule }
+ };
+ }
+
+ public long GetDeviceLocationName(ServiceCtx Context)
+ {
+ char[] TzName = TimeZone.Id.ToCharArray();
+
+ Context.ResponseData.Write(TzName);
+
+ int Padding = 0x24 - TzName.Length;
+
+ for (int Index = 0; Index < Padding; Index++)
+ {
+ Context.ResponseData.Write((byte)0);
+ }
+
+ return 0;
+ }
+
+ public long SetDeviceLocationName(ServiceCtx Context)
+ {
+ byte[] LocationName = Context.RequestData.ReadBytes(0x24);
+
+ string TzID = Encoding.ASCII.GetString(LocationName).TrimEnd('\0');
+
+ long ResultCode = 0;
+
+ try
+ {
+ TimeZone = TimeZoneInfo.FindSystemTimeZoneById(TzID);
+ }
+ catch (TimeZoneNotFoundException)
+ {
+ ResultCode = MakeError(ErrorModule.Time, 0x3dd);
+ }
+
+ return ResultCode;
+ }
+
+ public long GetTotalLocationNameCount(ServiceCtx Context)
+ {
+ Context.ResponseData.Write(TimeZoneInfo.GetSystemTimeZones().Count);
+
+ return 0;
+ }
+
+ public long LoadLocationNameList(ServiceCtx Context)
+ {
+ long BufferPosition = Context.Response.SendBuff[0].Position;
+ long BufferSize = Context.Response.SendBuff[0].Size;
+
+ int Offset = 0;
+
+ foreach (TimeZoneInfo info in TimeZoneInfo.GetSystemTimeZones())
+ {
+ byte[] TzData = Encoding.ASCII.GetBytes(info.Id);
+
+ Context.Memory.WriteBytes(BufferPosition + Offset, TzData);
+
+ int Padding = 0x24 - TzData.Length;
+
+ for (int Index = 0; Index < Padding; Index++)
+ {
+ Context.ResponseData.Write((byte)0);
+ }
+
+ Offset += 0x24;
+ }
+
+ return 0;
+ }
+
+ public long LoadTimeZoneRule(ServiceCtx Context)
+ {
+ long BufferPosition = Context.Request.ReceiveBuff[0].Position;
+ long BufferSize = Context.Request.ReceiveBuff[0].Size;
+
+ if (BufferSize != 0x4000)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)");
+ }
+
+ long ResultCode = 0;
+
+ byte[] LocationName = Context.RequestData.ReadBytes(0x24);
+
+ string TzID = Encoding.ASCII.GetString(LocationName).TrimEnd('\0');
+
+ // Check if the Time Zone exists, otherwise error out.
+ try
+ {
+ TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID);
+
+ byte[] TzData = Encoding.ASCII.GetBytes(Info.Id);
+
+ // FIXME: This is not in ANY cases accurate, but the games don't care about the content of the buffer, they only pass it.
+ // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
+ Context.Memory.WriteBytes(BufferPosition, TzData);
+ }
+ catch (TimeZoneNotFoundException)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})");
+
+ ResultCode = MakeError(ErrorModule.Time, 0x3dd);
+ }
+
+ return ResultCode;
+ }
+
+ private long ToCalendarTimeWithTz(ServiceCtx Context, long PosixTime, TimeZoneInfo Info)
+ {
+ DateTime CurrentTime = Epoch.AddSeconds(PosixTime);
+
+ CurrentTime = TimeZoneInfo.ConvertTimeFromUtc(CurrentTime, Info);
+
+ Context.ResponseData.Write((ushort)CurrentTime.Year);
+ Context.ResponseData.Write((byte)CurrentTime.Month);
+ Context.ResponseData.Write((byte)CurrentTime.Day);
+ Context.ResponseData.Write((byte)CurrentTime.Hour);
+ Context.ResponseData.Write((byte)CurrentTime.Minute);
+ Context.ResponseData.Write((byte)CurrentTime.Second);
+ Context.ResponseData.Write((byte)0); //MilliSecond ?
+ Context.ResponseData.Write((int)CurrentTime.DayOfWeek);
+ Context.ResponseData.Write(CurrentTime.DayOfYear - 1);
+ Context.ResponseData.Write(new byte[8]); //TODO: Find out the names used.
+ Context.ResponseData.Write((byte)(CurrentTime.IsDaylightSavingTime() ? 1 : 0));
+ Context.ResponseData.Write((int)Info.GetUtcOffset(CurrentTime).TotalSeconds);
+
+ return 0;
+ }
+
+ public long ToCalendarTime(ServiceCtx Context)
+ {
+ long PosixTime = Context.RequestData.ReadInt64();
+ long BufferPosition = Context.Request.SendBuff[0].Position;
+ long BufferSize = Context.Request.SendBuff[0].Size;
+
+ if (BufferSize != 0x4000)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)");
+ }
+
+ // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
+ byte[] TzData = Context.Memory.ReadBytes(BufferPosition, 0x24);
+
+ string TzID = Encoding.ASCII.GetString(TzData).TrimEnd('\0');
+
+ long ResultCode = 0;
+
+ // Check if the Time Zone exists, otherwise error out.
+ try
+ {
+ TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID);
+
+ ResultCode = ToCalendarTimeWithTz(Context, PosixTime, Info);
+ }
+ catch (TimeZoneNotFoundException)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})");
+
+ ResultCode = MakeError(ErrorModule.Time, 0x3dd);
+ }
+
+ return ResultCode;
+ }
+
+ public long ToCalendarTimeWithMyRule(ServiceCtx Context)
+ {
+ long PosixTime = Context.RequestData.ReadInt64();
+
+ return ToCalendarTimeWithTz(Context, PosixTime, TimeZone);
+ }
+
+ public long ToPosixTime(ServiceCtx Context)
+ {
+ long BufferPosition = Context.Request.SendBuff[0].Position;
+ long BufferSize = Context.Request.SendBuff[0].Size;
+
+ ushort Year = Context.RequestData.ReadUInt16();
+ byte Month = Context.RequestData.ReadByte();
+ byte Day = Context.RequestData.ReadByte();
+ byte Hour = Context.RequestData.ReadByte();
+ byte Minute = Context.RequestData.ReadByte();
+ byte Second = Context.RequestData.ReadByte();
+
+ DateTime CalendarTime = new DateTime(Year, Month, Day, Hour, Minute, Second);
+
+ if (BufferSize != 0x4000)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)");
+ }
+
+ // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
+ byte[] TzData = Context.Memory.ReadBytes(BufferPosition, 0x24);
+
+ string TzID = Encoding.ASCII.GetString(TzData).TrimEnd('\0');
+
+ long ResultCode = 0;
+
+ // Check if the Time Zone exists, otherwise error out.
+ try
+ {
+ TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID);
+
+ return ToPosixTimeWithTz(Context, CalendarTime, Info);
+ }
+ catch (TimeZoneNotFoundException)
+ {
+ Context.Device.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})");
+
+ ResultCode = MakeError(ErrorModule.Time, 0x3dd);
+ }
+
+ return ResultCode;
+ }
+
+ public long ToPosixTimeWithMyRule(ServiceCtx Context)
+ {
+ ushort Year = Context.RequestData.ReadUInt16();
+ byte Month = Context.RequestData.ReadByte();
+ byte Day = Context.RequestData.ReadByte();
+ byte Hour = Context.RequestData.ReadByte();
+ byte Minute = Context.RequestData.ReadByte();
+ byte Second = Context.RequestData.ReadByte();
+
+ DateTime CalendarTime = new DateTime(Year, Month, Day, Hour, Minute, Second, DateTimeKind.Local);
+
+ return ToPosixTimeWithTz(Context, CalendarTime, TimeZone);
+ }
+
+ private long ToPosixTimeWithTz(ServiceCtx Context, DateTime CalendarTime, TimeZoneInfo Info)
+ {
+ DateTime CalenderTimeUTC = TimeZoneInfo.ConvertTimeToUtc(CalendarTime, Info);
+
+ long PosixTime = ((DateTimeOffset)CalenderTimeUTC).ToUnixTimeSeconds();
+
+ long Position = Context.Request.RecvListBuff[0].Position;
+ long Size = Context.Request.RecvListBuff[0].Size;
+
+ Context.Memory.WriteInt64(Position, PosixTime);
+
+ Context.ResponseData.Write(1);
+
+ return 0;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs b/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs
new file mode 100644
index 00000000..54b7df3f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ enum SystemClockType
+ {
+ User,
+ Network,
+ Local,
+ EphemeralNetwork
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/Display.cs b/Ryujinx.HLE/HOS/Services/Vi/Display.cs
new file mode 100644
index 00000000..50fe5e1d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/Display.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class Display
+ {
+ public string Name { get; private set; }
+
+ public Display(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/GbpBuffer.cs b/Ryujinx.HLE/HOS/Services/Vi/GbpBuffer.cs
new file mode 100644
index 00000000..4f00c3de
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/GbpBuffer.cs
@@ -0,0 +1,60 @@
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services.Android
+{
+ struct GbpBuffer
+ {
+ public int Magic { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public int Stride { get; private set; }
+ public int Format { get; private set; }
+ public int Usage { get; private set; }
+
+ public int Pid { get; private set; }
+ public int RefCount { get; private set; }
+
+ public int FdsCount { get; private set; }
+ public int IntsCount { get; private set; }
+
+ public byte[] RawData { get; private set; }
+
+ public int Size => RawData.Length + 10 * 4;
+
+ public GbpBuffer(BinaryReader Reader)
+ {
+ Magic = Reader.ReadInt32();
+ Width = Reader.ReadInt32();
+ Height = Reader.ReadInt32();
+ Stride = Reader.ReadInt32();
+ Format = Reader.ReadInt32();
+ Usage = Reader.ReadInt32();
+
+ Pid = Reader.ReadInt32();
+ RefCount = Reader.ReadInt32();
+
+ FdsCount = Reader.ReadInt32();
+ IntsCount = Reader.ReadInt32();
+
+ RawData = Reader.ReadBytes((FdsCount + IntsCount) * 4);
+ }
+
+ public void Write(BinaryWriter Writer)
+ {
+ Writer.Write(Magic);
+ Writer.Write(Width);
+ Writer.Write(Height);
+ Writer.Write(Stride);
+ Writer.Write(Format);
+ Writer.Write(Usage);
+
+ Writer.Write(Pid);
+ Writer.Write(RefCount);
+
+ Writer.Write(FdsCount);
+ Writer.Write(IntsCount);
+
+ Writer.Write(RawData);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs
new file mode 100644
index 00000000..3006b73a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs
@@ -0,0 +1,233 @@
+using ChocolArm64.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using static Ryujinx.HLE.HOS.Services.Android.Parcel;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class IApplicationDisplayService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private IdDictionary Displays;
+
+ public IApplicationDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 100, GetRelayService },
+ { 101, GetSystemDisplayService },
+ { 102, GetManagerDisplayService },
+ { 103, GetIndirectDisplayTransactionService },
+ { 1000, ListDisplays },
+ { 1010, OpenDisplay },
+ { 1020, CloseDisplay },
+ { 1102, GetDisplayResolution },
+ { 2020, OpenLayer },
+ { 2021, CloseLayer },
+ { 2030, CreateStrayLayer },
+ { 2031, DestroyStrayLayer },
+ { 2101, SetLayerScalingMode },
+ { 5202, GetDisplayVSyncEvent }
+ };
+
+ Displays = new IdDictionary();
+ }
+
+ public long GetRelayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IHOSBinderDriver(Context.Device.Gpu.Renderer));
+
+ return 0;
+ }
+
+ public long GetSystemDisplayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new ISystemDisplayService());
+
+ return 0;
+ }
+
+ public long GetManagerDisplayService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IManagerDisplayService());
+
+ return 0;
+ }
+
+ public long GetIndirectDisplayTransactionService(ServiceCtx Context)
+ {
+ MakeObject(Context, new IHOSBinderDriver(Context.Device.Gpu.Renderer));
+
+ return 0;
+ }
+
+ public long ListDisplays(ServiceCtx Context)
+ {
+ long RecBuffPtr = Context.Request.ReceiveBuff[0].Position;
+
+ AMemoryHelper.FillWithZeros(Context.Memory, RecBuffPtr, 0x60);
+
+ //Add only the default display to buffer
+ Context.Memory.WriteBytes(RecBuffPtr, Encoding.ASCII.GetBytes("Default"));
+ Context.Memory.WriteInt64(RecBuffPtr + 0x40, 0x1L);
+ Context.Memory.WriteInt64(RecBuffPtr + 0x48, 0x1L);
+ Context.Memory.WriteInt64(RecBuffPtr + 0x50, 1920L);
+ Context.Memory.WriteInt64(RecBuffPtr + 0x58, 1080L);
+
+ Context.ResponseData.Write(1L);
+
+ return 0;
+ }
+
+ public long OpenDisplay(ServiceCtx Context)
+ {
+ string Name = GetDisplayName(Context);
+
+ long DisplayId = Displays.Add(new Display(Name));
+
+ Context.ResponseData.Write(DisplayId);
+
+ return 0;
+ }
+
+ public long CloseDisplay(ServiceCtx Context)
+ {
+ int DisplayId = Context.RequestData.ReadInt32();
+
+ Displays.Delete(DisplayId);
+
+ return 0;
+ }
+
+ public long GetDisplayResolution(ServiceCtx Context)
+ {
+ long DisplayId = Context.RequestData.ReadInt32();
+
+ Context.ResponseData.Write(1280);
+ Context.ResponseData.Write(720);
+
+ return 0;
+ }
+
+ public long OpenLayer(ServiceCtx Context)
+ {
+ long LayerId = Context.RequestData.ReadInt64();
+ long UserId = Context.RequestData.ReadInt64();
+
+ long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
+
+ byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
+
+ Context.Memory.WriteBytes(ParcelPtr, Parcel);
+
+ Context.ResponseData.Write((long)Parcel.Length);
+
+ return 0;
+ }
+
+ public long CloseLayer(ServiceCtx Context)
+ {
+ long LayerId = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public long CreateStrayLayer(ServiceCtx Context)
+ {
+ long LayerFlags = Context.RequestData.ReadInt64();
+ long DisplayId = Context.RequestData.ReadInt64();
+
+ long ParcelPtr = Context.Request.ReceiveBuff[0].Position;
+
+ Display Disp = Displays.GetData<Display>((int)DisplayId);
+
+ byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr);
+
+ Context.Memory.WriteBytes(ParcelPtr, Parcel);
+
+ Context.ResponseData.Write(0L);
+ Context.ResponseData.Write((long)Parcel.Length);
+
+ return 0;
+ }
+
+ public long DestroyStrayLayer(ServiceCtx Context)
+ {
+ return 0;
+ }
+
+ public long SetLayerScalingMode(ServiceCtx Context)
+ {
+ int ScalingMode = Context.RequestData.ReadInt32();
+ long Unknown = Context.RequestData.ReadInt64();
+
+ return 0;
+ }
+
+ public long GetDisplayVSyncEvent(ServiceCtx Context)
+ {
+ string Name = GetDisplayName(Context);
+
+ int Handle = Context.Process.HandleTable.OpenHandle(Context.Device.System.VsyncEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle);
+
+ return 0;
+ }
+
+ private byte[] MakeIGraphicsBufferProducer(long BasePtr)
+ {
+ long Id = 0x20;
+ long CookiePtr = 0L;
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ //flat_binder_object (size is 0x28)
+ Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER)
+ Writer.Write(0); //Flags
+ Writer.Write((int)(Id >> 0));
+ Writer.Write((int)(Id >> 32));
+ Writer.Write((int)(CookiePtr >> 0));
+ Writer.Write((int)(CookiePtr >> 32));
+ Writer.Write((byte)'d');
+ Writer.Write((byte)'i');
+ Writer.Write((byte)'s');
+ Writer.Write((byte)'p');
+ Writer.Write((byte)'d');
+ Writer.Write((byte)'r');
+ Writer.Write((byte)'v');
+ Writer.Write((byte)'\0');
+ Writer.Write(0L); //Pad
+
+ return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 });
+ }
+ }
+
+ private string GetDisplayName(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;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs
new file mode 100644
index 00000000..e86734e6
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs
@@ -0,0 +1,29 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class IApplicationRootService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IApplicationRootService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, GetDisplayService }
+ };
+ }
+
+ public long GetDisplayService(ServiceCtx Context)
+ {
+ int ServiceType = Context.RequestData.ReadInt32();
+
+ MakeObject(Context, new IApplicationDisplayService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs
new file mode 100644
index 00000000..bf5f20a2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs
@@ -0,0 +1,100 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Android;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class IHOSBinderDriver : IpcService, IDisposable
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ private KEvent ReleaseEvent;
+
+ private NvFlinger Flinger;
+
+ public IHOSBinderDriver(IGalRenderer Renderer)
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 0, TransactParcel },
+ { 1, AdjustRefcount },
+ { 2, GetNativeHandle },
+ { 3, TransactParcelAuto }
+ };
+
+ ReleaseEvent = new KEvent();
+
+ Flinger = new NvFlinger(Renderer, ReleaseEvent);
+ }
+
+ public long TransactParcel(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ int Code = Context.RequestData.ReadInt32();
+
+ long DataPos = Context.Request.SendBuff[0].Position;
+ long DataSize = Context.Request.SendBuff[0].Size;
+
+ byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize);
+
+ Data = Parcel.GetParcelData(Data);
+
+ return Flinger.ProcessParcelRequest(Context, Data, Code);
+ }
+
+ public long TransactParcelAuto(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ int Code = Context.RequestData.ReadInt32();
+
+ (long DataPos, long DataSize) = Context.Request.GetBufferType0x21();
+
+ byte[] Data = Context.Memory.ReadBytes(DataPos, DataSize);
+
+ Data = Parcel.GetParcelData(Data);
+
+ return Flinger.ProcessParcelRequest(Context, Data, Code);
+ }
+
+ public long AdjustRefcount(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ int AddVal = Context.RequestData.ReadInt32();
+ int Type = Context.RequestData.ReadInt32();
+
+ return 0;
+ }
+
+ public long GetNativeHandle(ServiceCtx Context)
+ {
+ int Id = Context.RequestData.ReadInt32();
+ uint Unk = Context.RequestData.ReadUInt32();
+
+ int Handle = Context.Process.HandleTable.OpenHandle(ReleaseEvent);
+
+ Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);
+
+ return 0;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ ReleaseEvent.Dispose();
+
+ Flinger.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs
new file mode 100644
index 00000000..61f0ffaa
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/IManagerDisplayService.cs
@@ -0,0 +1,54 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class IManagerDisplayService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManagerDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2010, CreateManagedLayer },
+ { 2011, DestroyManagedLayer },
+ { 6000, AddToLayerStack },
+ { 6002, SetLayerVisibility }
+ };
+ }
+
+ public static long CreateManagedLayer(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ Context.ResponseData.Write(0L); //LayerId
+
+ return 0;
+ }
+
+ public long DestroyManagedLayer(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ return 0;
+ }
+
+ public static long AddToLayerStack(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ return 0;
+ }
+
+ public static long SetLayerVisibility(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs
new file mode 100644
index 00000000..14b8fc3c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs
@@ -0,0 +1,29 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class IManagerRootService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public IManagerRootService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2, GetDisplayService }
+ };
+ }
+
+ public long GetDisplayService(ServiceCtx Context)
+ {
+ int ServiceType = Context.RequestData.ReadInt32();
+
+ MakeObject(Context, new IApplicationDisplayService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs
new file mode 100644
index 00000000..5657ba69
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/ISystemDisplayService.cs
@@ -0,0 +1,48 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.Logging;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class ISystemDisplayService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemDisplayService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 2205, SetLayerZ },
+ { 2207, SetLayerVisibility },
+ { 3200, GetDisplayMode }
+ };
+ }
+
+ public static long SetLayerZ(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ return 0;
+ }
+
+ public static long SetLayerVisibility(ServiceCtx Context)
+ {
+ Context.Device.Log.PrintStub(LogClass.ServiceVi, "Stubbed.");
+
+ return 0;
+ }
+
+ public static long GetDisplayMode(ServiceCtx Context)
+ {
+ //TODO: De-hardcode resolution.
+ Context.ResponseData.Write(1280);
+ Context.ResponseData.Write(720);
+ Context.ResponseData.Write(60.0f);
+ Context.ResponseData.Write(0);
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs b/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs
new file mode 100644
index 00000000..d9b7c220
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs
@@ -0,0 +1,29 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Vi
+{
+ class ISystemRootService : IpcService
+ {
+ private Dictionary<int, ServiceProcessRequest> m_Commands;
+
+ public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
+
+ public ISystemRootService()
+ {
+ m_Commands = new Dictionary<int, ServiceProcessRequest>()
+ {
+ { 1, GetDisplayService }
+ };
+ }
+
+ public long GetDisplayService(ServiceCtx Context)
+ {
+ int ServiceType = Context.RequestData.ReadInt32();
+
+ MakeObject(Context, new IApplicationDisplayService());
+
+ return 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs
new file mode 100644
index 00000000..12c17311
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs
@@ -0,0 +1,416 @@
+using Ryujinx.Graphics.Gal;
+using Ryujinx.HLE.Gpu.Texture;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Nv.NvMap;
+using Ryujinx.HLE.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+using static Ryujinx.HLE.HOS.Services.Android.Parcel;
+
+namespace Ryujinx.HLE.HOS.Services.Android
+{
+ class NvFlinger : IDisposable
+ {
+ private delegate long ServiceProcessParcel(ServiceCtx Context, BinaryReader ParcelReader);
+
+ private Dictionary<(string, int), ServiceProcessParcel> Commands;
+
+ private KEvent ReleaseEvent;
+
+ private IGalRenderer Renderer;
+
+ private const int BufferQueueCount = 0x40;
+ private const int BufferQueueMask = BufferQueueCount - 1;
+
+ [Flags]
+ private enum HalTransform
+ {
+ FlipX = 1 << 0,
+ FlipY = 1 << 1,
+ Rotate90 = 1 << 2
+ }
+
+ private enum BufferState
+ {
+ Free,
+ Dequeued,
+ Queued,
+ Acquired
+ }
+
+ private struct Rect
+ {
+ public int Top;
+ public int Left;
+ public int Right;
+ public int Bottom;
+ }
+
+ private struct BufferEntry
+ {
+ public BufferState State;
+
+ public HalTransform Transform;
+
+ public Rect Crop;
+
+ public GbpBuffer Data;
+ }
+
+ private BufferEntry[] BufferQueue;
+
+ private ManualResetEvent WaitBufferFree;
+
+ private bool Disposed;
+
+ public NvFlinger(IGalRenderer Renderer, KEvent ReleaseEvent)
+ {
+ Commands = new Dictionary<(string, int), ServiceProcessParcel>()
+ {
+ { ("android.gui.IGraphicBufferProducer", 0x1), GbpRequestBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x3), GbpDequeueBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x4), GbpDetachBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x7), GbpQueueBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x8), GbpCancelBuffer },
+ { ("android.gui.IGraphicBufferProducer", 0x9), GbpQuery },
+ { ("android.gui.IGraphicBufferProducer", 0xa), GbpConnect },
+ { ("android.gui.IGraphicBufferProducer", 0xb), GbpDisconnect },
+ { ("android.gui.IGraphicBufferProducer", 0xe), GbpPreallocBuffer }
+ };
+
+ this.Renderer = Renderer;
+ this.ReleaseEvent = ReleaseEvent;
+
+ BufferQueue = new BufferEntry[0x40];
+
+ WaitBufferFree = new ManualResetEvent(false);
+ }
+
+ public long ProcessParcelRequest(ServiceCtx Context, byte[] ParcelData, int Code)
+ {
+ using (MemoryStream MS = new MemoryStream(ParcelData))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ MS.Seek(4, SeekOrigin.Current);
+
+ int StrSize = Reader.ReadInt32();
+
+ string InterfaceName = Encoding.Unicode.GetString(Reader.ReadBytes(StrSize * 2));
+
+ long Remainder = MS.Position & 0xf;
+
+ if (Remainder != 0)
+ {
+ MS.Seek(0x10 - Remainder, SeekOrigin.Current);
+ }
+
+ MS.Seek(0x50, SeekOrigin.Begin);
+
+ if (Commands.TryGetValue((InterfaceName, Code), out ServiceProcessParcel ProcReq))
+ {
+ Context.Device.Log.PrintDebug(LogClass.ServiceVi, $"{InterfaceName} {ProcReq.Method.Name}");
+
+ return ProcReq(Context, Reader);
+ }
+ else
+ {
+ throw new NotImplementedException($"{InterfaceName} {Code}");
+ }
+ }
+ }
+
+ private long GbpRequestBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ int Slot = ParcelReader.ReadInt32();
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ BufferEntry Entry = BufferQueue[Slot];
+
+ int BufferCount = 1; //?
+ long BufferSize = Entry.Data.Size;
+
+ Writer.Write(BufferCount);
+ Writer.Write(BufferSize);
+
+ Entry.Data.Write(Writer);
+
+ Writer.Write(0);
+
+ return MakeReplyParcel(Context, MS.ToArray());
+ }
+ }
+
+ private long GbpDequeueBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ //TODO: Errors.
+ int Format = ParcelReader.ReadInt32();
+ int Width = ParcelReader.ReadInt32();
+ int Height = ParcelReader.ReadInt32();
+ int GetTimestamps = ParcelReader.ReadInt32();
+ int Usage = ParcelReader.ReadInt32();
+
+ int Slot = GetFreeSlotBlocking(Width, Height);
+
+ return MakeReplyParcel(Context, Slot, 1, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+
+ private long GbpQueueBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ Context.Device.Statistics.RecordGameFrameTime();
+
+ //TODO: Errors.
+ int Slot = ParcelReader.ReadInt32();
+ int Unknown4 = ParcelReader.ReadInt32();
+ int Unknown8 = ParcelReader.ReadInt32();
+ int Unknownc = ParcelReader.ReadInt32();
+ int Timestamp = ParcelReader.ReadInt32();
+ int IsAutoTimestamp = ParcelReader.ReadInt32();
+ int CropTop = ParcelReader.ReadInt32();
+ int CropLeft = ParcelReader.ReadInt32();
+ int CropRight = ParcelReader.ReadInt32();
+ int CropBottom = ParcelReader.ReadInt32();
+ int ScalingMode = ParcelReader.ReadInt32();
+ int Transform = ParcelReader.ReadInt32();
+ int StickyTransform = ParcelReader.ReadInt32();
+ int Unknown34 = ParcelReader.ReadInt32();
+ int Unknown38 = ParcelReader.ReadInt32();
+ int IsFenceValid = ParcelReader.ReadInt32();
+ int Fence0Id = ParcelReader.ReadInt32();
+ int Fence0Value = ParcelReader.ReadInt32();
+ int Fence1Id = ParcelReader.ReadInt32();
+ int Fence1Value = ParcelReader.ReadInt32();
+
+ BufferQueue[Slot].Transform = (HalTransform)Transform;
+
+ BufferQueue[Slot].Crop.Top = CropTop;
+ BufferQueue[Slot].Crop.Left = CropLeft;
+ BufferQueue[Slot].Crop.Right = CropRight;
+ BufferQueue[Slot].Crop.Bottom = CropBottom;
+
+ BufferQueue[Slot].State = BufferState.Queued;
+
+ SendFrameBuffer(Context, Slot);
+
+ return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+ }
+
+ private long GbpDetachBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ return MakeReplyParcel(Context, 0);
+ }
+
+ private long GbpCancelBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ //TODO: Errors.
+ int Slot = ParcelReader.ReadInt32();
+
+ BufferQueue[Slot].State = BufferState.Free;
+
+ return MakeReplyParcel(Context, 0);
+ }
+
+ private long GbpQuery(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ return MakeReplyParcel(Context, 0, 0);
+ }
+
+ private long GbpConnect(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ return MakeReplyParcel(Context, 1280, 720, 0, 0, 0);
+ }
+
+ private long GbpDisconnect(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ return MakeReplyParcel(Context, 0);
+ }
+
+ private long GbpPreallocBuffer(ServiceCtx Context, BinaryReader ParcelReader)
+ {
+ int Slot = ParcelReader.ReadInt32();
+
+ int BufferCount = ParcelReader.ReadInt32();
+
+ if (BufferCount > 0)
+ {
+ long BufferSize = ParcelReader.ReadInt64();
+
+ BufferQueue[Slot].State = BufferState.Free;
+
+ BufferQueue[Slot].Data = new GbpBuffer(ParcelReader);
+ }
+
+ return MakeReplyParcel(Context, 0);
+ }
+
+ private long MakeReplyParcel(ServiceCtx Context, params int[] Ints)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ foreach (int Int in Ints)
+ {
+ Writer.Write(Int);
+ }
+
+ return MakeReplyParcel(Context, MS.ToArray());
+ }
+ }
+
+ private long MakeReplyParcel(ServiceCtx Context, byte[] Data)
+ {
+ long ReplyPos = Context.Request.ReceiveBuff[0].Position;
+ long ReplySize = Context.Request.ReceiveBuff[0].Size;
+
+ byte[] Reply = MakeParcel(Data, new byte[0]);
+
+ Context.Memory.WriteBytes(ReplyPos, Reply);
+
+ return 0;
+ }
+
+ private void SendFrameBuffer(ServiceCtx Context, int Slot)
+ {
+ int FbWidth = BufferQueue[Slot].Data.Width;
+ int FbHeight = BufferQueue[Slot].Data.Height;
+
+ int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c);
+ int BufferOffset = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x50);
+
+ NvMapHandle Map = NvMapIoctl.GetNvMap(Context, NvMapHandle);;
+
+ long FbAddr = Map.Address + BufferOffset;
+
+ BufferQueue[Slot].State = BufferState.Acquired;
+
+ Rect Crop = BufferQueue[Slot].Crop;
+
+ bool FlipX = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipX);
+ bool FlipY = BufferQueue[Slot].Transform.HasFlag(HalTransform.FlipY);
+
+ //Rotation is being ignored
+
+ int Top = Crop.Top;
+ int Left = Crop.Left;
+ int Right = Crop.Right;
+ int Bottom = Crop.Bottom;
+
+ Renderer.QueueAction(() => Renderer.FrameBuffer.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom));
+
+ //TODO: Support double buffering here aswell, it is broken for GPU
+ //frame buffers because it seems to be completely out of sync.
+ if (Context.Device.Gpu.Engine3d.IsFrameBufferPosition(FbAddr))
+ {
+ //Frame buffer is rendered to by the GPU, we can just
+ //bind the frame buffer texture, it's not necessary to read anything.
+ Renderer.QueueAction(() => Renderer.FrameBuffer.Set(FbAddr));
+ }
+ else
+ {
+ //Frame buffer is not set on the GPU registers, in this case
+ //assume that the app is manually writing to it.
+ TextureInfo Texture = new TextureInfo(FbAddr, FbWidth, FbHeight);
+
+ byte[] Data = TextureReader.Read(Context.Memory, Texture);
+
+ Renderer.QueueAction(() => Renderer.FrameBuffer.Set(Data, FbWidth, FbHeight));
+ }
+
+ Context.Device.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot));
+ }
+
+ private void ReleaseBuffer(int Slot)
+ {
+ BufferQueue[Slot].State = BufferState.Free;
+
+ ReleaseEvent.WaitEvent.Set();
+
+ lock (WaitBufferFree)
+ {
+ WaitBufferFree.Set();
+ }
+ }
+
+ private int GetFreeSlotBlocking(int Width, int Height)
+ {
+ int Slot;
+
+ do
+ {
+ lock (WaitBufferFree)
+ {
+ if ((Slot = GetFreeSlot(Width, Height)) != -1)
+ {
+ break;
+ }
+
+ if (Disposed)
+ {
+ break;
+ }
+
+ WaitBufferFree.Reset();
+ }
+
+ WaitBufferFree.WaitOne();
+ }
+ while (!Disposed);
+
+ return Slot;
+ }
+
+ private int GetFreeSlot(int Width, int Height)
+ {
+ lock (BufferQueue)
+ {
+ for (int Slot = 0; Slot < BufferQueue.Length; Slot++)
+ {
+ if (BufferQueue[Slot].State != BufferState.Free)
+ {
+ continue;
+ }
+
+ GbpBuffer Data = BufferQueue[Slot].Data;
+
+ if (Data.Width == Width &&
+ Data.Height == Height)
+ {
+ BufferQueue[Slot].State = BufferState.Dequeued;
+
+ return Slot;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing && !Disposed)
+ {
+ Disposed = true;
+
+ lock (WaitBufferFree)
+ {
+ WaitBufferFree.Set();
+ }
+
+ WaitBufferFree.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Vi/Parcel.cs b/Ryujinx.HLE/HOS/Services/Vi/Parcel.cs
new file mode 100644
index 00000000..bb842795
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Vi/Parcel.cs
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Services.Android
+{
+ static class Parcel
+ {
+ public static byte[] GetParcelData(byte[] Parcel)
+ {
+ if (Parcel == null)
+ {
+ throw new ArgumentNullException(nameof(Parcel));
+ }
+
+ using (MemoryStream MS = new MemoryStream(Parcel))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ int DataSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int ObjsSize = Reader.ReadInt32();
+ int ObjsOffset = Reader.ReadInt32();
+
+ MS.Seek(DataOffset - 0x10, SeekOrigin.Current);
+
+ return Reader.ReadBytes(DataSize);
+ }
+ }
+
+ public static byte[] MakeParcel(byte[] Data, byte[] Objs)
+ {
+ if (Data == null)
+ {
+ throw new ArgumentNullException(nameof(Data));
+ }
+
+ if (Objs == null)
+ {
+ throw new ArgumentNullException(nameof(Objs));
+ }
+
+ using (MemoryStream MS = new MemoryStream())
+ {
+ BinaryWriter Writer = new BinaryWriter(MS);
+
+ Writer.Write(Data.Length);
+ Writer.Write(0x10);
+ Writer.Write(Objs.Length);
+ Writer.Write(Data.Length + 0x10);
+
+ Writer.Write(Data);
+ Writer.Write(Objs);
+
+ return MS.ToArray();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs
new file mode 100644
index 00000000..ee0e6fea
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs
@@ -0,0 +1,62 @@
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Services.Am;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ class AppletStateMgr : IDisposable
+ {
+ private ConcurrentQueue<MessageInfo> Messages;
+
+ public FocusState FocusState { get; private set; }
+
+ public KEvent MessageEvent { get; private set; }
+
+ public AppletStateMgr()
+ {
+ Messages = new ConcurrentQueue<MessageInfo>();
+
+ MessageEvent = new KEvent();
+ }
+
+ public void SetFocus(bool IsFocused)
+ {
+ FocusState = IsFocused
+ ? FocusState.InFocus
+ : FocusState.OutOfFocus;
+
+ EnqueueMessage(MessageInfo.FocusStateChanged);
+ }
+
+ public void EnqueueMessage(MessageInfo Message)
+ {
+ Messages.Enqueue(Message);
+
+ MessageEvent.WaitEvent.Set();
+ }
+
+ public bool TryDequeueMessage(out MessageInfo Message)
+ {
+ if (Messages.Count < 2)
+ {
+ MessageEvent.WaitEvent.Reset();
+ }
+
+ return Messages.TryDequeue(out Message);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool Disposing)
+ {
+ if (Disposing)
+ {
+ MessageEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/SystemState/ColorSet.cs b/Ryujinx.HLE/HOS/SystemState/ColorSet.cs
new file mode 100644
index 00000000..4d7a7e2f
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/SystemState/OpenCloseState.cs b/Ryujinx.HLE/HOS/SystemState/OpenCloseState.cs
new file mode 100644
index 00000000..a2678b5c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/OpenCloseState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public enum OpenCloseState
+ {
+ Closed,
+ Open
+ }
+}
diff --git a/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs b/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs
new file mode 100644
index 00000000..2046ed62
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs
@@ -0,0 +1,23 @@
+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
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
new file mode 100644
index 00000000..bd1dbd78
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+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"
+ };
+
+ internal static string[] AudioOutputs = new string[]
+ {
+ "AudioTvOutput",
+ "AudioStereoJackOutput",
+ "AudioBuiltInSpeakerOutput"
+ };
+
+ internal long DesiredLanguageCode { get; private set; }
+
+ internal string ActiveAudioOutput { get; private set; }
+
+ public bool DockedMode { get; set; }
+
+ public ColorSet ThemeColor { get; set; }
+
+ private ConcurrentDictionary<string, UserProfile> Profiles;
+
+ internal UserProfile LastOpenUser { get; private set; }
+
+ public SystemStateMgr()
+ {
+ SetLanguage(SystemLanguage.AmericanEnglish);
+
+ SetAudioOutputAsBuiltInSpeaker();
+
+ Profiles = new ConcurrentDictionary<string, UserProfile>();
+
+ UserId DefaultUuid = new UserId("00000000000000000000000000000001");
+
+ AddUser(DefaultUuid, "Player");
+ OpenUser(DefaultUuid);
+ }
+
+ public void SetLanguage(SystemLanguage Language)
+ {
+ DesiredLanguageCode = GetLanguageCode((int)Language);
+ }
+
+ public void SetAudioOutputAsTv()
+ {
+ ActiveAudioOutput = AudioOutputs[0];
+ }
+
+ public void SetAudioOutputAsStereoJack()
+ {
+ ActiveAudioOutput = AudioOutputs[1];
+ }
+
+ public void SetAudioOutputAsBuiltInSpeaker()
+ {
+ ActiveAudioOutput = AudioOutputs[2];
+ }
+
+ public void AddUser(UserId Uuid, string Name)
+ {
+ UserProfile Profile = new UserProfile(Uuid, Name);
+
+ Profiles.AddOrUpdate(Uuid.UserIdHex, Profile, (Key, Old) => Profile);
+ }
+
+ public void OpenUser(UserId Uuid)
+ {
+ if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile))
+ {
+ (LastOpenUser = Profile).AccountState = OpenCloseState.Open;
+ }
+ }
+
+ public void CloseUser(UserId Uuid)
+ {
+ if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile))
+ {
+ Profile.AccountState = OpenCloseState.Closed;
+ }
+ }
+
+ public int GetUserCount()
+ {
+ return Profiles.Count;
+ }
+
+ internal bool TryGetUser(UserId Uuid, out UserProfile Profile)
+ {
+ return Profiles.TryGetValue(Uuid.UserIdHex, out Profile);
+ }
+
+ internal IEnumerable<UserProfile> GetAllUsers()
+ {
+ return Profiles.Values;
+ }
+
+ internal IEnumerable<UserProfile> GetOpenUsers()
+ {
+ return Profiles.Values.Where(x => x.AccountState == OpenCloseState.Open);
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/SystemState/UserId.cs b/Ryujinx.HLE/HOS/SystemState/UserId.cs
new file mode 100644
index 00000000..1e7c53dd
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/UserId.cs
@@ -0,0 +1,76 @@
+using Ryujinx.HLE.Utilities;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ public struct UserId
+ {
+ public string UserIdHex { get; private set; }
+
+ public byte[] Bytes { get; private set; }
+
+ public UserId(long Low, long High)
+ {
+ if ((Low | High) == 0)
+ {
+ throw new ArgumentException("Zero is not a valid user id!");
+ }
+
+ byte[] Bytes = new byte[16];
+
+ int Index = Bytes.Length;
+
+ void WriteBytes(long Value)
+ {
+ for (int Byte = 0; Byte < 8; Byte++)
+ {
+ Bytes[--Index] = (byte)(Value >> Byte * 8);
+ }
+ }
+
+ WriteBytes(Low);
+ WriteBytes(High);
+
+ UserIdHex = string.Empty;
+
+ foreach (byte Byte in Bytes)
+ {
+ UserIdHex += Byte.ToString("X2");
+ }
+
+ this.Bytes = Bytes;
+ }
+
+ public UserId(string UserIdHex)
+ {
+ if (UserIdHex == null || UserIdHex.Length != 32 || !UserIdHex.All("0123456789abcdefABCDEF".Contains))
+ {
+ throw new ArgumentException("Invalid user id!", nameof(UserIdHex));
+ }
+
+ if (UserIdHex == "00000000000000000000000000000000")
+ {
+ throw new ArgumentException("Zero is not a valid user id!", nameof(UserIdHex));
+ }
+
+ this.UserIdHex = UserIdHex.ToUpper();
+
+ Bytes = StringUtils.HexToBytes(UserIdHex);
+ }
+
+ internal void Write(BinaryWriter Writer)
+ {
+ for (int Index = Bytes.Length - 1; Index >= 0; Index--)
+ {
+ Writer.Write(Bytes[Index]);
+ }
+ }
+
+ public override string ToString()
+ {
+ return UserIdHex;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/SystemState/UserProfile.cs b/Ryujinx.HLE/HOS/SystemState/UserProfile.cs
new file mode 100644
index 00000000..63852cdf
--- /dev/null
+++ b/Ryujinx.HLE/HOS/SystemState/UserProfile.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.SystemState
+{
+ class UserProfile
+ {
+ private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public UserId Uuid { get; private set; }
+
+ public string Name { get; private set; }
+
+ public long LastModifiedTimestamp { get; private set; }
+
+ public OpenCloseState AccountState { get; set; }
+ public OpenCloseState OnlinePlayState { get; set; }
+
+ public UserProfile(UserId Uuid, string Name)
+ {
+ this.Uuid = Uuid;
+ this.Name = Name;
+
+ LastModifiedTimestamp = 0;
+
+ AccountState = OpenCloseState.Closed;
+ OnlinePlayState = OpenCloseState.Closed;
+
+ UpdateTimestamp();
+ }
+
+ private void UpdateTimestamp()
+ {
+ LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds;
+ }
+ }
+}