Horizon.cs 13 KB

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