using LibHac; using LibHac.Bcat; using LibHac.Fs; using LibHac.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.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Font; 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.Arp; 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.UserManager; 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.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.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; 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 KernelContext KernelContext { get; } internal Switch Device { get; private set; } 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 ServerBase BsdServer { get; private set; } internal ServerBase AudRenServer { get; private set; } internal ServerBase AudOutServer { 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 SharedFontManager Font { 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 LibHac.Horizon LibHacHorizonServer { get; private set; } internal HorizonClient LibHacHorizonClient { get; private set; } public Horizon(Switch device) { KernelContext = new KernelContext( 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; KPageList hidPageList = new KPageList(); KPageList fontPageList = new KPageList(); KPageList iirsPageList = new KPageList(); KPageList timePageList = 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); var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList); var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList); var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList); var timeStorage = new SharedMemoryStorage(KernelContext, timePageList); 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); AppletState = new AppletStateMgr(this); AppletState.SetFocus(true); Font = new SharedFontManager(device, fontStorage); VsyncEvent = new KEvent(KernelContext); DisplayResolutionChangeEvent = new KEvent(KernelContext); AccountManager = device.Configuration.AccountManager; ContentManager = device.Configuration.ContentManager; CaptureManager = new CaptureManager(device); // 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(Guid.NewGuid().ToByteArray()); IRtcManager.GetExternalRtcValue(out ulong rtcValue); // We assume the rtc is system time. TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); // Configure and setup internal offset TimeSpanType internalOffset = TimeSpanType.FromSeconds(device.Configuration.SystemTimeOffset); TimeSpanType systemTimeOffset = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds); if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(3600L); } else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime()) { internalOffset = internalOffset.AddSeconds(-3600L); } internalOffset = new TimeSpanType(-internalOffset.NanoSeconds); // First init the standard steady clock TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false); TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds()); if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) { TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); // The network system clock needs a valid system clock, as such we setup this system clock using the local system clock. TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(null, out SystemClockContext localSytemClockContext); TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy); } TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); // FIXME: TimeZone shoud be init here but it's actually done in ContentManager TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); DatabaseImpl.Instance.InitializeDatabase(device); HostSyncpoint = new NvHostSyncpt(device); SurfaceFlinger = new SurfaceFlinger(device); InitLibHacHorizon(); InitializeAudioRenderer(); } private void InitializeAudioRenderer() { AudioManager = new AudioManager(); AudioOutputManager = new AudioOutputManager(); AudioInputManager = new AudioInputManager(); AudioRendererManager = new AudioRendererManager(); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) { KEvent registerBufferEvent = new KEvent(KernelContext); audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); } AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); 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(); } public void InitializeServices() { IUserInterface sm = new IUserInterface(KernelContext); sm.TrySetServer(new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext))); // Wait until SM server thread is done with initialization, // only then doing connections to SM is safe. sm.Server.InitDone.WaitOne(); sm.Server.InitDone.Dispose(); BsdServer = new ServerBase(KernelContext, "BsdServer"); AudRenServer = new ServerBase(KernelContext, "AudioRendererServer"); AudOutServer = new ServerBase(KernelContext, "AudioOutServer"); 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"); } public void LoadKip(string kipPath) { using IStorage kipFile = new LocalStorage(kipPath, FileAccess.Read); ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile)); } private void InitLibHacHorizon() { LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer); horizon.CreateHorizonClient(out HorizonClient ryujinxClient).ThrowIfFailure(); horizon.CreateHorizonClient(out HorizonClient bcatClient).ThrowIfFailure(); ryujinxClient.Sm.RegisterService(new LibHacIReader(this), "arp:r").ThrowIfFailure(); new BcatServer(bcatClient); LibHacHorizonServer = horizon; LibHacHorizonClient = ryujinxClient; } public void ChangeDockedModeState(bool newState) { if (newState != State.DockedMode) { State.DockedMode = newState; PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; AppletState.Messages.Enqueue(MessageInfo.OperationModeChanged); AppletState.Messages.Enqueue(MessageInfo.PerformanceModeChanged); AppletState.MessageEvent.ReadableEvent.Signal(); SignalDisplayResolutionChange(); Device.Configuration.RefreshInputConfig?.Invoke(); } } public void ReturnFocus() { AppletState.SetFocus(true); } public void SimulateWakeUpMessage() { AppletState.Messages.Enqueue(MessageInfo.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; 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.Flags.HasFlag(ProcessCreationFlags.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.Flags.HasFlag(ProcessCreationFlags.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(); KernelContext.Dispose(); } } } }