Horizon.cs 16 KB

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