Horizon.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. using LibHac;
  2. using Ryujinx.Common.Logging;
  3. using Ryujinx.HLE.HOS.Font;
  4. using Ryujinx.HLE.HOS.Kernel;
  5. using Ryujinx.HLE.HOS.SystemState;
  6. using Ryujinx.HLE.Loaders.Executables;
  7. using Ryujinx.HLE.Loaders.Npdm;
  8. using System;
  9. using System.Collections.Concurrent;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. using Nso = Ryujinx.HLE.Loaders.Executables.Nso;
  14. namespace Ryujinx.HLE.HOS
  15. {
  16. public class Horizon : IDisposable
  17. {
  18. internal const int HidSize = 0x40000;
  19. internal const int FontSize = 0x1100000;
  20. private Switch Device;
  21. private ConcurrentDictionary<int, Process> Processes;
  22. public SystemStateMgr State { get; private set; }
  23. internal KRecursiveLock CriticalSectionLock { get; private set; }
  24. internal KScheduler Scheduler { get; private set; }
  25. internal KTimeManager TimeManager { get; private set; }
  26. internal KAddressArbiter AddressArbiter { get; private set; }
  27. internal KSynchronization Synchronization { get; private set; }
  28. internal LinkedList<KThread> Withholders { get; private set; }
  29. internal KSharedMemory HidSharedMem { get; private set; }
  30. internal KSharedMemory FontSharedMem { get; private set; }
  31. internal SharedFontManager Font { get; private set; }
  32. internal KEvent VsyncEvent { get; private set; }
  33. internal Keyset KeySet { get; private set; }
  34. private bool HasStarted;
  35. public Nacp ControlData { get; set; }
  36. public string CurrentTitle { get; private set; }
  37. public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
  38. public Horizon(Switch Device)
  39. {
  40. this.Device = Device;
  41. Processes = new ConcurrentDictionary<int, Process>();
  42. State = new SystemStateMgr();
  43. CriticalSectionLock = new KRecursiveLock(this);
  44. Scheduler = new KScheduler(this);
  45. TimeManager = new KTimeManager();
  46. AddressArbiter = new KAddressArbiter(this);
  47. Synchronization = new KSynchronization(this);
  48. Withholders = new LinkedList<KThread>();
  49. Scheduler.StartAutoPreemptionThread();
  50. if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) ||
  51. !Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA))
  52. {
  53. throw new InvalidOperationException();
  54. }
  55. HidSharedMem = new KSharedMemory(HidPA, HidSize);
  56. FontSharedMem = new KSharedMemory(FontPA, FontSize);
  57. Font = new SharedFontManager(Device, FontSharedMem.PA);
  58. VsyncEvent = new KEvent(this);
  59. LoadKeySet();
  60. }
  61. public void LoadCart(string ExeFsDir, string RomFsFile = null)
  62. {
  63. if (RomFsFile != null)
  64. {
  65. Device.FileSystem.LoadRomFs(RomFsFile);
  66. }
  67. string NpdmFileName = Path.Combine(ExeFsDir, "main.npdm");
  68. Npdm MetaData = null;
  69. if (File.Exists(NpdmFileName))
  70. {
  71. Logger.PrintInfo(LogClass.Loader, $"Loading main.npdm...");
  72. using (FileStream Input = new FileStream(NpdmFileName, FileMode.Open))
  73. {
  74. MetaData = new Npdm(Input);
  75. }
  76. }
  77. else
  78. {
  79. Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
  80. }
  81. Process MainProcess = MakeProcess(MetaData);
  82. void LoadNso(string FileName)
  83. {
  84. foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
  85. {
  86. if (Path.GetExtension(File) != string.Empty)
  87. {
  88. continue;
  89. }
  90. Logger.PrintInfo(LogClass.Loader, $"Loading {Path.GetFileNameWithoutExtension(File)}...");
  91. using (FileStream Input = new FileStream(File, FileMode.Open))
  92. {
  93. string Name = Path.GetFileNameWithoutExtension(File);
  94. Nso Program = new Nso(Input, Name);
  95. MainProcess.LoadProgram(Program);
  96. }
  97. }
  98. }
  99. if (!(MainProcess.MetaData?.Is64Bits ?? true))
  100. {
  101. throw new NotImplementedException("32-bit titles are unsupported!");
  102. }
  103. CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16");
  104. LoadNso("rtld");
  105. MainProcess.SetEmptyArgs();
  106. LoadNso("main");
  107. LoadNso("subsdk*");
  108. LoadNso("sdk");
  109. MainProcess.Run();
  110. }
  111. public void LoadXci(string XciFile)
  112. {
  113. FileStream File = new FileStream(XciFile, FileMode.Open, FileAccess.Read);
  114. Xci Xci = new Xci(KeySet, File);
  115. (Nca MainNca, Nca ControlNca) = GetXciGameData(Xci);
  116. if (MainNca == null)
  117. {
  118. Logger.PrintError(LogClass.Loader, "Unable to load XCI");
  119. return;
  120. }
  121. LoadNca(MainNca, ControlNca);
  122. }
  123. private (Nca Main, Nca Control) GetXciGameData(Xci Xci)
  124. {
  125. if (Xci.SecurePartition == null)
  126. {
  127. throw new InvalidDataException("Could not find XCI secure partition");
  128. }
  129. Nca MainNca = null;
  130. Nca PatchNca = null;
  131. Nca ControlNca = null;
  132. foreach (PfsFileEntry TicketEntry in Xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".tik")))
  133. {
  134. Ticket ticket = new Ticket(Xci.SecurePartition.OpenFile(TicketEntry));
  135. if (!KeySet.TitleKeys.ContainsKey(ticket.RightsId))
  136. {
  137. KeySet.TitleKeys.Add(ticket.RightsId, ticket.GetTitleKey(KeySet));
  138. }
  139. }
  140. foreach (PfsFileEntry FileEntry in Xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca")))
  141. {
  142. Stream NcaStream = Xci.SecurePartition.OpenFile(FileEntry);
  143. Nca Nca = new Nca(KeySet, NcaStream, true);
  144. if (Nca.Header.ContentType == ContentType.Program)
  145. {
  146. if (Nca.Sections.Any(x => x?.Type == SectionType.Romfs))
  147. {
  148. MainNca = Nca;
  149. }
  150. else if (Nca.Sections.Any(x => x?.Type == SectionType.Bktr))
  151. {
  152. PatchNca = Nca;
  153. }
  154. }
  155. else if (Nca.Header.ContentType == ContentType.Control)
  156. {
  157. ControlNca = Nca;
  158. }
  159. }
  160. if (MainNca == null)
  161. {
  162. Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file");
  163. }
  164. MainNca.SetBaseNca(PatchNca);
  165. if (ControlNca != null)
  166. {
  167. ReadControlData(ControlNca);
  168. }
  169. if (PatchNca != null)
  170. {
  171. PatchNca.SetBaseNca(MainNca);
  172. return (PatchNca, ControlNca);
  173. }
  174. return (MainNca, ControlNca);
  175. }
  176. public void ReadControlData(Nca ControlNca)
  177. {
  178. Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false, FsIntegrityCheckLevel));
  179. byte[] ControlFile = ControlRomfs.GetFile("/control.nacp");
  180. BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile));
  181. ControlData = new Nacp(Reader);
  182. }
  183. public void LoadNca(string NcaFile)
  184. {
  185. FileStream File = new FileStream(NcaFile, FileMode.Open, FileAccess.Read);
  186. Nca Nca = new Nca(KeySet, File, true);
  187. LoadNca(Nca, null);
  188. }
  189. public void LoadNsp(string NspFile)
  190. {
  191. FileStream File = new FileStream(NspFile, FileMode.Open, FileAccess.Read);
  192. Pfs Nsp = new Pfs(File);
  193. PfsFileEntry TicketFile = Nsp.Files.FirstOrDefault(x => x.Name.EndsWith(".tik"));
  194. // Load title key from the NSP's ticket in case the user doesn't have a title key file
  195. if (TicketFile != null)
  196. {
  197. Ticket Ticket = new Ticket(Nsp.OpenFile(TicketFile));
  198. KeySet.TitleKeys[Ticket.RightsId] = Ticket.GetTitleKey(KeySet);
  199. }
  200. Nca MainNca = null;
  201. Nca ControlNca = null;
  202. foreach (PfsFileEntry NcaFile in Nsp.Files.Where(x => x.Name.EndsWith(".nca")))
  203. {
  204. Nca Nca = new Nca(KeySet, Nsp.OpenFile(NcaFile), true);
  205. if (Nca.Header.ContentType == ContentType.Program)
  206. {
  207. MainNca = Nca;
  208. }
  209. else if (Nca.Header.ContentType == ContentType.Control)
  210. {
  211. ControlNca = Nca;
  212. }
  213. }
  214. if (MainNca != null)
  215. {
  216. LoadNca(MainNca, ControlNca);
  217. return;
  218. }
  219. Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided NSP file");
  220. }
  221. public void LoadNca(Nca MainNca, Nca ControlNca)
  222. {
  223. if (MainNca.Header.ContentType != ContentType.Program)
  224. {
  225. Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
  226. return;
  227. }
  228. Stream RomfsStream = MainNca.OpenSection(ProgramPartitionType.Data, false, FsIntegrityCheckLevel);
  229. Stream ExefsStream = MainNca.OpenSection(ProgramPartitionType.Code, false, FsIntegrityCheckLevel);
  230. if (ExefsStream == null)
  231. {
  232. Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA");
  233. return;
  234. }
  235. if (RomfsStream == null)
  236. {
  237. Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
  238. }
  239. else
  240. {
  241. Device.FileSystem.SetRomFs(RomfsStream);
  242. }
  243. Pfs Exefs = new Pfs(ExefsStream);
  244. Npdm MetaData = null;
  245. if (Exefs.FileExists("main.npdm"))
  246. {
  247. Logger.PrintInfo(LogClass.Loader, "Loading main.npdm...");
  248. MetaData = new Npdm(Exefs.OpenFile("main.npdm"));
  249. }
  250. else
  251. {
  252. Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!");
  253. }
  254. Process MainProcess = MakeProcess(MetaData);
  255. void LoadNso(string Filename)
  256. {
  257. foreach (PfsFileEntry File in Exefs.Files.Where(x => x.Name.StartsWith(Filename)))
  258. {
  259. if (Path.GetExtension(File.Name) != string.Empty)
  260. {
  261. continue;
  262. }
  263. Logger.PrintInfo(LogClass.Loader, $"Loading {Filename}...");
  264. string Name = Path.GetFileNameWithoutExtension(File.Name);
  265. Nso Program = new Nso(Exefs.OpenFile(File), Name);
  266. MainProcess.LoadProgram(Program);
  267. }
  268. }
  269. Nacp ReadControlData()
  270. {
  271. Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false, FsIntegrityCheckLevel));
  272. byte[] ControlFile = ControlRomfs.GetFile("/control.nacp");
  273. BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile));
  274. Nacp ControlData = new Nacp(Reader);
  275. CurrentTitle = ControlData.Languages[(int)State.DesiredTitleLanguage].Title;
  276. if (string.IsNullOrWhiteSpace(CurrentTitle))
  277. {
  278. CurrentTitle = ControlData.Languages.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
  279. }
  280. return ControlData;
  281. }
  282. if (ControlNca != null)
  283. {
  284. MainProcess.ControlData = ReadControlData();
  285. }
  286. else
  287. {
  288. CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16");
  289. }
  290. if (!MainProcess.MetaData.Is64Bits)
  291. {
  292. throw new NotImplementedException("32-bit titles are unsupported!");
  293. }
  294. LoadNso("rtld");
  295. MainProcess.SetEmptyArgs();
  296. LoadNso("main");
  297. LoadNso("subsdk");
  298. LoadNso("sdk");
  299. MainProcess.Run();
  300. }
  301. public void LoadProgram(string FilePath)
  302. {
  303. bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro";
  304. string Name = Path.GetFileNameWithoutExtension(FilePath);
  305. string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath);
  306. if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/")))
  307. {
  308. string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}";
  309. string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath);
  310. string SwitchDir = Path.GetDirectoryName(TempPath);
  311. if (!Directory.Exists(SwitchDir))
  312. {
  313. Directory.CreateDirectory(SwitchDir);
  314. }
  315. File.Copy(FilePath, TempPath, true);
  316. FilePath = TempPath;
  317. }
  318. Process MainProcess = MakeProcess();
  319. using (FileStream Input = new FileStream(FilePath, FileMode.Open))
  320. {
  321. MainProcess.LoadProgram(IsNro
  322. ? (IExecutable)new Nro(Input, FilePath)
  323. : (IExecutable)new Nso(Input, FilePath));
  324. }
  325. MainProcess.SetEmptyArgs();
  326. MainProcess.Run(IsNro);
  327. }
  328. public void LoadKeySet()
  329. {
  330. string KeyFile = null;
  331. string TitleKeyFile = null;
  332. string ConsoleKeyFile = null;
  333. string Home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
  334. LoadSetAtPath(Path.Combine(Home, ".switch"));
  335. LoadSetAtPath(Device.FileSystem.GetSystemPath());
  336. KeySet = ExternalKeys.ReadKeyFile(KeyFile, TitleKeyFile, ConsoleKeyFile);
  337. void LoadSetAtPath(string BasePath)
  338. {
  339. string LocalKeyFile = Path.Combine(BasePath, "prod.keys");
  340. string LocalTitleKeyFile = Path.Combine(BasePath, "title.keys");
  341. string LocalConsoleKeyFile = Path.Combine(BasePath, "console.keys");
  342. if (File.Exists(LocalKeyFile))
  343. {
  344. KeyFile = LocalKeyFile;
  345. }
  346. if (File.Exists(LocalTitleKeyFile))
  347. {
  348. TitleKeyFile = LocalTitleKeyFile;
  349. }
  350. if (File.Exists(LocalConsoleKeyFile))
  351. {
  352. ConsoleKeyFile = LocalConsoleKeyFile;
  353. }
  354. }
  355. }
  356. public void SignalVsync()
  357. {
  358. VsyncEvent.ReadableEvent.Signal();
  359. }
  360. private Process MakeProcess(Npdm MetaData = null)
  361. {
  362. HasStarted = true;
  363. Process Process;
  364. lock (Processes)
  365. {
  366. int ProcessId = 0;
  367. while (Processes.ContainsKey(ProcessId))
  368. {
  369. ProcessId++;
  370. }
  371. Process = new Process(Device, ProcessId, MetaData);
  372. Processes.TryAdd(ProcessId, Process);
  373. }
  374. InitializeProcess(Process);
  375. return Process;
  376. }
  377. private void InitializeProcess(Process Process)
  378. {
  379. Process.AppletState.SetFocus(true);
  380. }
  381. internal void ExitProcess(int ProcessId)
  382. {
  383. if (Processes.TryRemove(ProcessId, out Process Process))
  384. {
  385. Process.Dispose();
  386. if (Processes.Count == 0)
  387. {
  388. Scheduler.Dispose();
  389. TimeManager.Dispose();
  390. Device.Unload();
  391. }
  392. }
  393. }
  394. public void EnableMultiCoreScheduling()
  395. {
  396. if (!HasStarted)
  397. {
  398. Scheduler.MultiCoreScheduling = true;
  399. }
  400. }
  401. public void DisableMultiCoreScheduling()
  402. {
  403. if (!HasStarted)
  404. {
  405. Scheduler.MultiCoreScheduling = false;
  406. }
  407. }
  408. public void Dispose()
  409. {
  410. Dispose(true);
  411. }
  412. protected virtual void Dispose(bool Disposing)
  413. {
  414. if (Disposing)
  415. {
  416. foreach (Process Process in Processes.Values)
  417. {
  418. Process.Dispose();
  419. }
  420. }
  421. }
  422. }
  423. }