Horizon.cs 17 KB

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