using LibHac.Common; using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.Tools.FsSystem; using Ryujinx.Audio; using Ryujinx.Audio.Input; using Ryujinx.Audio.Integration; using Ryujinx.Audio.Output; using Ryujinx.Audio.Renderer.Device; using Ryujinx.Audio.Renderer.Server; using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; using Ryujinx.HLE.HOS.Services.Pcv.Bpc; using Ryujinx.HLE.HOS.Services.Sdb.Pl; using Ryujinx.HLE.HOS.Services.Settings; using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.Services.SurfaceFlinger; using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using TimeSpanType = Ryujinx.HLE.HOS.Services.Time.Clock.TimeSpanType; namespace Ryujinx.HLE.HOS { using TimeServiceManager = Services.Time.TimeManager; public class Horizon : IDisposable { internal const int HidSize = 0x40000; internal const int FontSize = 0x1100000; internal const int IirsSize = 0x8000; internal const int TimeSize = 0x1000; internal const int AppletCaptureBufferSize = 0x384000; internal KernelContext KernelContext { get; } internal Switch Device { get; private set; } internal ITickSource TickSource { get; } internal SurfaceFlinger SurfaceFlinger { get; private set; } internal AudioManager AudioManager { get; private set; } internal AudioOutputManager AudioOutputManager { get; private set; } internal AudioInputManager AudioInputManager { get; private set; } internal AudioRendererManager AudioRendererManager { get; private set; } internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; } public SystemStateMgr State { get; private set; } internal PerformanceState PerformanceState { get; private set; } internal AppletStateMgr AppletState { get; private set; } internal List NfpDevices { get; private set; } internal SmRegistry SmRegistry { get; private set; } internal ServerBase SmServer { get; private set; } internal ServerBase BsdServer { get; private set; } internal ServerBase AudRenServer { get; private set; } internal ServerBase AudOutServer { get; private set; } internal ServerBase FsServer { get; private set; } internal ServerBase HidServer { get; private set; } internal ServerBase NvDrvServer { get; private set; } internal ServerBase TimeServer { get; private set; } internal ServerBase ViServer { get; private set; } internal ServerBase ViServerM { get; private set; } internal ServerBase ViServerS { get; private set; } internal KSharedMemory HidSharedMem { get; private set; } internal KSharedMemory FontSharedMem { get; private set; } internal KSharedMemory IirsSharedMem { get; private set; } internal KTransferMemory AppletCaptureBufferTransfer { get; private set; } internal SharedFontManager SharedFontManager { get; private set; } internal AccountManager AccountManager { get; private set; } internal ContentManager ContentManager { get; private set; } internal CaptureManager CaptureManager { get; private set; } internal KEvent VsyncEvent { get; private set; } internal KEvent DisplayResolutionChangeEvent { get; private set; } public KeySet KeySet => Device.FileSystem.KeySet; private bool _isDisposed; public bool EnablePtc { get; set; } public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } public int GlobalAccessLogMode { get; set; } internal SharedMemoryStorage HidStorage { get; private set; } internal NvHostSyncpt HostSyncpoint { get; private set; } internal LibHacHorizonManager LibHacHorizonManager { get; private set; } internal ServiceTable ServiceTable { get; private set; } public bool IsPaused { get; private set; } public Horizon(Switch device) { TickSource = new TickSource(KernelConstants.CounterFrequency); KernelContext = new KernelContext( TickSource, device, device.Memory, device.Configuration.MemoryConfiguration.ToKernelMemorySize(), device.Configuration.MemoryConfiguration.ToKernelMemoryArrange()); Device = device; State = new SystemStateMgr(); PerformanceState = new PerformanceState(); NfpDevices = new List(); // Note: This is not really correct, but with HLE of services, the only memory // region used that is used is Application, so we can use the other ones for anything. KMemoryRegionManager region = KernelContext.MemoryManager.MemoryRegions[(int)MemoryRegion.NvServices]; ulong hidPa = region.Address; ulong fontPa = region.Address + HidSize; ulong iirsPa = region.Address + HidSize + FontSize; ulong timePa = region.Address + HidSize + FontSize + IirsSize; ulong appletCaptureBufferPa = region.Address + HidSize + FontSize + IirsSize + TimeSize; KPageList hidPageList = new KPageList(); KPageList fontPageList = new KPageList(); KPageList iirsPageList = new KPageList(); KPageList timePageList = new KPageList(); KPageList appletCaptureBufferPageList = new KPageList(); hidPageList.AddRange(hidPa, HidSize / KPageTableBase.PageSize); fontPageList.AddRange(fontPa, FontSize / KPageTableBase.PageSize); iirsPageList.AddRange(iirsPa, IirsSize / KPageTableBase.PageSize); timePageList.AddRange(timePa, TimeSize / KPageTableBase.PageSize); appletCaptureBufferPageList.AddRange(appletCaptureBufferPa, AppletCaptureBufferSize / KPageTableBase.PageSize); var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList); var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList); var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList); var timeStorage = new SharedMemoryStorage(KernelContext, timePageList); var appletCaptureBufferStorage = new SharedMemoryStorage(KernelContext, appletCaptureBufferPageList); HidStorage = hidStorage; HidSharedMem = new KSharedMemory(KernelContext, hidStorage, 0, 0, KMemoryPermission.Read); FontSharedMem = new KSharedMemory(KernelContext, fontStorage, 0, 0, KMemoryPermission.Read); IirsSharedMem = new KSharedMemory(KernelContext, iirsStorage, 0, 0, KMemoryPermission.Read); KSharedMemory timeSharedMemory = new KSharedMemory(KernelContext, timeStorage, 0, 0, KMemoryPermission.Read); TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timeStorage, TimeSize); AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage); AppletState = new AppletStateMgr(this); AppletState.SetFocus(true); VsyncEvent = new KEvent(KernelContext); DisplayResolutionChangeEvent = new KEvent(KernelContext); SharedFontManager = new SharedFontManager(device, fontStorage); AccountManager = device.Configuration.AccountManager; ContentManager = device.Configuration.ContentManager; CaptureManager = new CaptureManager(device); LibHacHorizonManager = device.Configuration.LibHacHorizonManager; // We hardcode a clock source id to avoid it changing between each start. // TODO: use set:sys (and get external clock source id from settings) // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. UInt128 clockSourceId = new UInt128(0x36a0328702ce8bc1, 0x1608eaba02333284); IRtcManager.GetExternalRtcValue(out ulong rtcValue); // We assume the rtc is system time. TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); // Configure and setup internal offset TimeSpanType internalOffset = TimeSpanType.FromSeconds(device.Configuration.SystemTimeOffset); TimeSpanType systemTimeOffset = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds); if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(3600L); } else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(-3600L); } systemTime = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds); // First init the standard steady clock TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, TimeSpanType.Zero, TimeSpanType.Zero, TimeSpanType.Zero, false); TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds()); TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext); if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) { TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); // The network system clock needs a valid system clock, as such we setup this system clock using the local system clock. TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy); } TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, true, localSytemClockContext.SteadyTimePoint); // FIXME: TimeZone should be init here but it's actually done in ContentManager TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient); HostSyncpoint = new NvHostSyncpt(device); SurfaceFlinger = new SurfaceFlinger(device); InitializeAudioRenderer(TickSource); InitializeServices(); } private void InitializeAudioRenderer(ITickSource tickSource) { AudioManager = new AudioManager(); AudioOutputManager = new AudioOutputManager(); AudioInputManager = new AudioInputManager(); AudioRendererManager = new AudioRendererManager(tickSource); AudioRendererManager.SetVolume(Device.Configuration.AudioVolume); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver); IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) { KEvent registerBufferEvent = new KEvent(KernelContext); audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); } AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); AudioOutputManager.SetVolume(Device.Configuration.AudioVolume); IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) { KEvent registerBufferEvent = new KEvent(KernelContext); audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); } AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents); IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; for (int i = 0; i < systemEvents.Length; i++) { KEvent systemEvent = new KEvent(KernelContext); systemEvents[i] = new AudioKernelEvent(systemEvent); } AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver); AudioManager.Start(); } private void InitializeServices() { SmRegistry = new SmRegistry(); SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); // Wait until SM server thread is done with initialization, // only then doing connections to SM is safe. SmServer.InitDone.WaitOne(); BsdServer = new ServerBase(KernelContext, "BsdServer"); AudRenServer = new ServerBase(KernelContext, "AudioRendererServer"); AudOutServer = new ServerBase(KernelContext, "AudioOutServer"); FsServer = new ServerBase(KernelContext, "FsServer"); HidServer = new ServerBase(KernelContext, "HidServer"); NvDrvServer = new ServerBase(KernelContext, "NvservicesServer"); TimeServer = new ServerBase(KernelContext, "TimeServer"); ViServer = new ServerBase(KernelContext, "ViServerU"); ViServerM = new ServerBase(KernelContext, "ViServerM"); ViServerS = new ServerBase(KernelContext, "ViServerS"); StartNewServices(); } private void StartNewServices() { ServiceTable = new ServiceTable(); var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient)); foreach (var service in services) { const ProcessCreationFlags Flags = ProcessCreationFlags.EnableAslr | ProcessCreationFlags.AddressSpace64Bit | ProcessCreationFlags.Is64Bit | ProcessCreationFlags.PoolPartitionSystem; ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); uint[] defaultCapabilities = new uint[] { 0x030363F7, 0x1FFFFFCF, 0x207FFFEF, 0x47E0060F, 0x0048BFFF, 0x01007FFF }; // TODO: // - Pass enough information (capabilities, process creation info, etc) on ServiceEntry for proper initialization. // - Have the ThreadStart function take the syscall, address space and thread context parameters instead of passing them here. KernelStatic.StartInitialProcess(KernelContext, creationInfo, defaultCapabilities, 44, () => { service.Start(KernelContext.Syscall, KernelStatic.GetCurrentProcess().CpuMemory, KernelStatic.GetCurrentThread().ThreadContext); }); } } public bool LoadKip(string kipPath) { using var kipFile = new SharedRef(new LocalStorage(kipPath, FileAccess.Read)); return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile)); } public void ChangeDockedModeState(bool newState) { if (newState != State.DockedMode) { State.DockedMode = newState; PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; AppletState.Messages.Enqueue(AppletMessage.OperationModeChanged); AppletState.Messages.Enqueue(AppletMessage.PerformanceModeChanged); AppletState.MessageEvent.ReadableEvent.Signal(); SignalDisplayResolutionChange(); Device.Configuration.RefreshInputConfig?.Invoke(); } } public void SetVolume(float volume) { AudioOutputManager.SetVolume(volume); AudioRendererManager.SetVolume(volume); } public float GetVolume() { return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume(); } public void ReturnFocus() { AppletState.SetFocus(true); } public void SimulateWakeUpMessage() { AppletState.Messages.Enqueue(AppletMessage.Resume); AppletState.MessageEvent.ReadableEvent.Signal(); } public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) { if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; NfpDevices[nfpDeviceId].AmiiboId = amiiboId; NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid; } } public bool SearchingForAmiibo(out int nfpDeviceId) { nfpDeviceId = default; for (int i = 0; i < NfpDevices.Count; i++) { if (NfpDevices[i].State == NfpDeviceState.SearchingForTag) { nfpDeviceId = i; return true; } } return false; } public void SignalDisplayResolutionChange() { DisplayResolutionChangeEvent.ReadableEvent.Signal(); } public void SignalVsync() { VsyncEvent.ReadableEvent.Signal(); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (!_isDisposed && disposing) { _isDisposed = true; // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop. if (IsPaused) { AudioManager.StopUpdates(); TogglePauseEmulation(false); AudioRendererManager.StopSendingCommands(); } KProcess terminationProcess = new KProcess(KernelContext); KThread terminationThread = new KThread(KernelContext); terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () => { // Force all threads to exit. lock (KernelContext.Processes) { // Terminate application. foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.IsApplication)) { process.Terminate(); process.DecrementReferenceCount(); } // The application existed, now surface flinger can exit too. SurfaceFlinger.Dispose(); // Terminate HLE services (must be done after the application is already terminated, // otherwise the application will receive errors due to service termination). foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.IsApplication)) { process.Terminate(); process.DecrementReferenceCount(); } KernelContext.Processes.Clear(); } // Exit ourself now! KernelStatic.GetCurrentThread().Exit(); }); terminationThread.Start(); // Wait until the thread is actually started. while (terminationThread.HostThread.ThreadState == ThreadState.Unstarted) { Thread.Sleep(10); } // Wait until the termination thread is done terminating all the other threads. terminationThread.HostThread.Join(); // Destroy nvservices channels as KThread could be waiting on some user events. // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. INvDrvServices.Destroy(); AudioManager.Dispose(); AudioOutputManager.Dispose(); AudioInputManager.Dispose(); AudioRendererManager.Dispose(); if (LibHacHorizonManager.ApplicationClient != null) { LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); } KernelContext.Dispose(); } } public void TogglePauseEmulation(bool pause) { lock (KernelContext.Processes) { foreach (KProcess process in KernelContext.Processes.Values) { if (process.IsApplication) { // Only game process should be paused. process.SetActivity(pause); } } if (pause && !IsPaused) { Device.AudioDeviceDriver.GetPauseEvent().Reset(); TickSource.Suspend(); } else if (!pause && IsPaused) { Device.AudioDeviceDriver.GetPauseEvent().Set(); TickSource.Resume(); } } IsPaused = pause; } } }