/ src / Ryujinx.HLE / HOS / Horizon.cs
Horizon.cs
  1  using LibHac.Common;
  2  using LibHac.Common.Keys;
  3  using LibHac.Fs;
  4  using LibHac.Fs.Shim;
  5  using LibHac.FsSystem;
  6  using LibHac.Tools.FsSystem;
  7  using Ryujinx.Cpu;
  8  using Ryujinx.HLE.FileSystem;
  9  using Ryujinx.HLE.HOS.Kernel;
 10  using Ryujinx.HLE.HOS.Kernel.Memory;
 11  using Ryujinx.HLE.HOS.Kernel.Process;
 12  using Ryujinx.HLE.HOS.Kernel.Threading;
 13  using Ryujinx.HLE.HOS.Services;
 14  using Ryujinx.HLE.HOS.Services.Account.Acc;
 15  using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
 16  using Ryujinx.HLE.HOS.Services.Apm;
 17  using Ryujinx.HLE.HOS.Services.Caps;
 18  using Ryujinx.HLE.HOS.Services.Mii;
 19  using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
 20  using Ryujinx.HLE.HOS.Services.Nv;
 21  using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
 22  using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
 23  using Ryujinx.HLE.HOS.Services.Sdb.Pl;
 24  using Ryujinx.HLE.HOS.Services.Settings;
 25  using Ryujinx.HLE.HOS.Services.Sm;
 26  using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
 27  using Ryujinx.HLE.HOS.Services.Time.Clock;
 28  using Ryujinx.HLE.HOS.SystemState;
 29  using Ryujinx.HLE.Loaders.Executables;
 30  using Ryujinx.HLE.Loaders.Processes;
 31  using Ryujinx.Horizon;
 32  using System;
 33  using System.Collections.Generic;
 34  using System.IO;
 35  using System.Linq;
 36  using System.Threading;
 37  
 38  namespace Ryujinx.HLE.HOS
 39  {
 40      using TimeServiceManager = Services.Time.TimeManager;
 41  
 42      public class Horizon : IDisposable
 43      {
 44          internal const int HidSize = 0x40000;
 45          internal const int FontSize = 0x1100000;
 46          internal const int IirsSize = 0x8000;
 47          internal const int TimeSize = 0x1000;
 48          internal const int AppletCaptureBufferSize = 0x384000;
 49  
 50          internal KernelContext KernelContext { get; }
 51  
 52          internal Switch Device { get; private set; }
 53  
 54          internal ITickSource TickSource { get; }
 55  
 56          internal SurfaceFlinger SurfaceFlinger { get; private set; }
 57  
 58          public SystemStateMgr State { get; private set; }
 59  
 60          internal PerformanceState PerformanceState { get; private set; }
 61  
 62          internal AppletStateMgr AppletState { get; private set; }
 63  
 64          internal List<NfpDevice> NfpDevices { get; private set; }
 65  
 66          internal SmRegistry SmRegistry { get; private set; }
 67  
 68          internal ServerBase SmServer { get; private set; }
 69          internal ServerBase BsdServer { get; private set; }
 70          internal ServerBase FsServer { get; private set; }
 71          internal ServerBase HidServer { get; private set; }
 72          internal ServerBase NvDrvServer { get; private set; }
 73          internal ServerBase TimeServer { get; private set; }
 74          internal ServerBase ViServer { get; private set; }
 75          internal ServerBase ViServerM { get; private set; }
 76          internal ServerBase ViServerS { get; private set; }
 77          internal ServerBase LdnServer { get; private set; }
 78  
 79          internal KSharedMemory HidSharedMem { get; private set; }
 80          internal KSharedMemory FontSharedMem { get; private set; }
 81          internal KSharedMemory IirsSharedMem { get; private set; }
 82  
 83          internal KTransferMemory AppletCaptureBufferTransfer { get; private set; }
 84  
 85          internal SharedFontManager SharedFontManager { get; private set; }
 86          internal AccountManager AccountManager { get; private set; }
 87          internal ContentManager ContentManager { get; private set; }
 88          internal CaptureManager CaptureManager { get; private set; }
 89  
 90          internal KEvent VsyncEvent { get; private set; }
 91  
 92          internal KEvent DisplayResolutionChangeEvent { get; private set; }
 93  
 94          public KeySet KeySet => Device.FileSystem.KeySet;
 95  
 96          private bool _isDisposed;
 97  
 98          public bool EnablePtc { get; set; }
 99  
100          public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
101  
102          public int GlobalAccessLogMode { get; set; }
103  
104          internal SharedMemoryStorage HidStorage { get; private set; }
105  
106          internal NvHostSyncpt HostSyncpoint { get; private set; }
107  
108          internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
109  
110          internal ServiceTable ServiceTable { get; private set; }
111  
112          public bool IsPaused { get; private set; }
113  
114          public Horizon(Switch device)
115          {
116              TickSource = new TickSource(KernelConstants.CounterFrequency);
117  
118              KernelContext = new KernelContext(
119                  TickSource,
120                  device,
121                  device.Memory,
122                  device.Configuration.MemoryConfiguration.ToKernelMemorySize(),
123                  device.Configuration.MemoryConfiguration.ToKernelMemoryArrange());
124  
125              Device = device;
126  
127              State = new SystemStateMgr();
128  
129              PerformanceState = new PerformanceState();
130  
131              NfpDevices = new List<NfpDevice>();
132  
133              // Note: This is not really correct, but with HLE of services, the only memory
134              // region used that is used is Application, so we can use the other ones for anything.
135              KMemoryRegionManager region = KernelContext.MemoryManager.MemoryRegions[(int)MemoryRegion.NvServices];
136  
137              ulong hidPa = region.Address;
138              ulong fontPa = region.Address + HidSize;
139              ulong iirsPa = region.Address + HidSize + FontSize;
140              ulong timePa = region.Address + HidSize + FontSize + IirsSize;
141              ulong appletCaptureBufferPa = region.Address + HidSize + FontSize + IirsSize + TimeSize;
142  
143              KPageList hidPageList = new();
144              KPageList fontPageList = new();
145              KPageList iirsPageList = new();
146              KPageList timePageList = new();
147              KPageList appletCaptureBufferPageList = new();
148  
149              hidPageList.AddRange(hidPa, HidSize / KPageTableBase.PageSize);
150              fontPageList.AddRange(fontPa, FontSize / KPageTableBase.PageSize);
151              iirsPageList.AddRange(iirsPa, IirsSize / KPageTableBase.PageSize);
152              timePageList.AddRange(timePa, TimeSize / KPageTableBase.PageSize);
153              appletCaptureBufferPageList.AddRange(appletCaptureBufferPa, AppletCaptureBufferSize / KPageTableBase.PageSize);
154  
155              var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList);
156              var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList);
157              var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList);
158              var timeStorage = new SharedMemoryStorage(KernelContext, timePageList);
159              var appletCaptureBufferStorage = new SharedMemoryStorage(KernelContext, appletCaptureBufferPageList);
160  
161              HidStorage = hidStorage;
162  
163              HidSharedMem = new KSharedMemory(KernelContext, hidStorage, 0, 0, KMemoryPermission.Read);
164              FontSharedMem = new KSharedMemory(KernelContext, fontStorage, 0, 0, KMemoryPermission.Read);
165              IirsSharedMem = new KSharedMemory(KernelContext, iirsStorage, 0, 0, KMemoryPermission.Read);
166  
167              KSharedMemory timeSharedMemory = new(KernelContext, timeStorage, 0, 0, KMemoryPermission.Read);
168  
169              TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timeStorage, TimeSize);
170  
171              AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage);
172  
173              AppletState = new AppletStateMgr(this);
174  
175              AppletState.SetFocus(true);
176  
177              VsyncEvent = new KEvent(KernelContext);
178  
179              DisplayResolutionChangeEvent = new KEvent(KernelContext);
180  
181              SharedFontManager = new SharedFontManager(device, fontStorage);
182              AccountManager = device.Configuration.AccountManager;
183              ContentManager = device.Configuration.ContentManager;
184              CaptureManager = new CaptureManager(device);
185  
186              LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
187  
188              // We hardcode a clock source id to avoid it changing between each start.
189              // TODO: use set:sys (and get external clock source id from settings)
190              // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
191              UInt128 clockSourceId = new(0x36a0328702ce8bc1, 0x1608eaba02333284);
192              IRtcManager.GetExternalRtcValue(out ulong rtcValue);
193  
194              // We assume the rtc is system time.
195              TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue);
196  
197              // Configure and setup internal offset
198              TimeSpanType internalOffset = TimeSpanType.FromSeconds(device.Configuration.SystemTimeOffset);
199  
200              TimeSpanType systemTimeOffset = new(systemTime.NanoSeconds + internalOffset.NanoSeconds);
201  
202              if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime())
203              {
204                  internalOffset = internalOffset.AddSeconds(3600L);
205              }
206              else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime())
207              {
208                  internalOffset = internalOffset.AddSeconds(-3600L);
209              }
210  
211              systemTime = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds);
212  
213              // First init the standard steady clock
214              TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, TimeSpanType.Zero, TimeSpanType.Zero, TimeSpanType.Zero, false);
215              TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
216              TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
217  
218              if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
219              {
220                  TimeSpanType standardNetworkClockSufficientAccuracy = new((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
221  
222                  // The network system clock needs a valid system clock, as such we setup this system clock using the local system clock.
223                  TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy);
224              }
225  
226              TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, true, localSytemClockContext.SteadyTimePoint);
227  
228              // FIXME: TimeZone should be init here but it's actually done in ContentManager
229  
230              TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
231  
232              DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient);
233  
234              HostSyncpoint = new NvHostSyncpt(device);
235  
236              SurfaceFlinger = new SurfaceFlinger(device);
237          }
238  
239          public void InitializeServices()
240          {
241              SmRegistry = new SmRegistry();
242              SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
243  
244              // Wait until SM server thread is done with initialization,
245              // only then doing connections to SM is safe.
246              SmServer.InitDone.WaitOne();
247  
248              BsdServer = new ServerBase(KernelContext, "BsdServer");
249              FsServer = new ServerBase(KernelContext, "FsServer");
250              HidServer = new ServerBase(KernelContext, "HidServer");
251              NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
252              TimeServer = new ServerBase(KernelContext, "TimeServer");
253              ViServer = new ServerBase(KernelContext, "ViServerU");
254              ViServerM = new ServerBase(KernelContext, "ViServerM");
255              ViServerS = new ServerBase(KernelContext, "ViServerS");
256              LdnServer = new ServerBase(KernelContext, "LdnServer");
257  
258              StartNewServices();
259          }
260  
261          private void StartNewServices()
262          {
263              HorizonFsClient fsClient = new(this);
264  
265              ServiceTable = new ServiceTable();
266              var services = ServiceTable.GetServices(new HorizonOptions
267                  (Device.Configuration.IgnoreMissingServices,
268                  LibHacHorizonManager.BcatClient,
269                  fsClient,
270                  AccountManager,
271                  Device.AudioDeviceDriver,
272                  TickSource));
273  
274              foreach (var service in services)
275              {
276                  const ProcessCreationFlags Flags =
277                      ProcessCreationFlags.EnableAslr |
278                      ProcessCreationFlags.AddressSpace64Bit |
279                      ProcessCreationFlags.Is64Bit |
280                      ProcessCreationFlags.PoolPartitionSystem;
281  
282                  ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
283  
284                  uint[] defaultCapabilities = {
285                      0x030363F7,
286                      0x1FFFFFCF,
287                      0x207FFFEF,
288                      0x47E0060F,
289                      0x0048BFFF,
290                      0x01007FFF,
291                  };
292  
293                  // TODO:
294                  // - Pass enough information (capabilities, process creation info, etc) on ServiceEntry for proper initialization.
295                  // - Have the ThreadStart function take the syscall, address space and thread context parameters instead of passing them here.
296                  KernelStatic.StartInitialProcess(KernelContext, creationInfo, defaultCapabilities, 44, () =>
297                  {
298                      service.Start(KernelContext.Syscall, KernelStatic.GetCurrentProcess().CpuMemory, KernelStatic.GetCurrentThread().ThreadContext);
299                  });
300              }
301          }
302  
303          public bool LoadKip(string kipPath)
304          {
305              using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
306  
307              return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
308          }
309  
310          public void ChangeDockedModeState(bool newState)
311          {
312              if (newState != State.DockedMode)
313              {
314                  State.DockedMode = newState;
315                  PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
316  
317                  AppletState.Messages.Enqueue(AppletMessage.OperationModeChanged);
318                  AppletState.Messages.Enqueue(AppletMessage.PerformanceModeChanged);
319                  AppletState.MessageEvent.ReadableEvent.Signal();
320  
321                  SignalDisplayResolutionChange();
322  
323                  Device.Configuration.RefreshInputConfig?.Invoke();
324              }
325          }
326  
327          public void ReturnFocus()
328          {
329              AppletState.SetFocus(true);
330          }
331  
332          public void SimulateWakeUpMessage()
333          {
334              AppletState.Messages.Enqueue(AppletMessage.Resume);
335              AppletState.MessageEvent.ReadableEvent.Signal();
336          }
337  
338          public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid)
339          {
340              if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag)
341              {
342                  NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound;
343                  NfpDevices[nfpDeviceId].AmiiboId = amiiboId;
344                  NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid;
345              }
346          }
347  
348          public bool SearchingForAmiibo(out int nfpDeviceId)
349          {
350              nfpDeviceId = default;
351  
352              for (int i = 0; i < NfpDevices.Count; i++)
353              {
354                  if (NfpDevices[i].State == NfpDeviceState.SearchingForTag)
355                  {
356                      nfpDeviceId = i;
357  
358                      return true;
359                  }
360              }
361  
362              return false;
363          }
364  
365          public void SignalDisplayResolutionChange()
366          {
367              DisplayResolutionChangeEvent.ReadableEvent.Signal();
368          }
369  
370          public void SignalVsync()
371          {
372              VsyncEvent.ReadableEvent.Signal();
373          }
374  
375          public void Dispose()
376          {
377              GC.SuppressFinalize(this);
378              Dispose(true);
379          }
380  
381          protected virtual void Dispose(bool disposing)
382          {
383              if (!_isDisposed && disposing)
384              {
385                  _isDisposed = true;
386  
387                  // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
388                  if (IsPaused)
389                  {
390                      TogglePauseEmulation(false);
391                  }
392  
393                  KProcess terminationProcess = new(KernelContext);
394                  KThread terminationThread = new(KernelContext);
395  
396                  terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () =>
397                  {
398                      // Force all threads to exit.
399                      lock (KernelContext.Processes)
400                      {
401                          // Terminate application.
402                          foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.IsApplication))
403                          {
404                              process.Terminate();
405                              process.DecrementReferenceCount();
406                          }
407  
408                          // The application existed, now surface flinger can exit too.
409                          SurfaceFlinger.Dispose();
410  
411                          // Terminate HLE services (must be done after the application is already terminated,
412                          // otherwise the application will receive errors due to service termination).
413                          foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.IsApplication))
414                          {
415                              process.Terminate();
416                              process.DecrementReferenceCount();
417                          }
418  
419                          KernelContext.Processes.Clear();
420                      }
421  
422                      // Exit ourself now!
423                      KernelStatic.GetCurrentThread().Exit();
424                  });
425  
426                  terminationThread.Start();
427  
428                  // Wait until the thread is actually started.
429                  while (terminationThread.HostThread.ThreadState == ThreadState.Unstarted)
430                  {
431                      Thread.Sleep(10);
432                  }
433  
434                  // Wait until the termination thread is done terminating all the other threads.
435                  terminationThread.HostThread.Join();
436  
437                  // Destroy nvservices channels as KThread could be waiting on some user events.
438                  // 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.
439                  INvDrvServices.Destroy();
440  
441                  if (LibHacHorizonManager.ApplicationClient != null)
442                  {
443                      LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
444                  }
445  
446                  KernelContext.Dispose();
447              }
448          }
449  
450          public void TogglePauseEmulation(bool pause)
451          {
452              lock (KernelContext.Processes)
453              {
454                  foreach (KProcess process in KernelContext.Processes.Values)
455                  {
456                      if (process.IsApplication)
457                      {
458                          // Only game process should be paused.
459                          process.SetActivity(pause);
460                      }
461                  }
462  
463                  if (pause && !IsPaused)
464                  {
465                      Device.AudioDeviceDriver.GetPauseEvent().Reset();
466                      TickSource.Suspend();
467                  }
468                  else if (!pause && IsPaused)
469                  {
470                      Device.AudioDeviceDriver.GetPauseEvent().Set();
471                      TickSource.Resume();
472                  }
473              }
474              IsPaused = pause;
475          }
476      }
477  }