Horizon.cs 14 KB

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