Horizon.cs 16 KB

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