ProcessLoaderHelper.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. using LibHac.Account;
  2. using LibHac.Common;
  3. using LibHac.Fs;
  4. using LibHac.Fs.Shim;
  5. using LibHac.FsSystem;
  6. using LibHac.Loader;
  7. using LibHac.Ncm;
  8. using LibHac.Ns;
  9. using LibHac.Tools.Fs;
  10. using LibHac.Tools.FsSystem;
  11. using LibHac.Tools.FsSystem.NcaUtils;
  12. using Ryujinx.Common;
  13. using Ryujinx.Common.Logging;
  14. using Ryujinx.HLE.HOS;
  15. using Ryujinx.HLE.HOS.Kernel;
  16. using Ryujinx.HLE.HOS.Kernel.Common;
  17. using Ryujinx.HLE.HOS.Kernel.Memory;
  18. using Ryujinx.HLE.HOS.Kernel.Process;
  19. using Ryujinx.HLE.Loaders.Executables;
  20. using Ryujinx.HLE.Loaders.Processes.Extensions;
  21. using Ryujinx.Horizon.Common;
  22. using System;
  23. using System.Linq;
  24. using System.Runtime.InteropServices;
  25. using ApplicationId = LibHac.Ncm.ApplicationId;
  26. namespace Ryujinx.HLE.Loaders.Processes
  27. {
  28. static class ProcessLoaderHelper
  29. {
  30. public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem)
  31. {
  32. ulong applicationId = 0;
  33. int programCount = 0;
  34. Span<bool> hasIndex = stackalloc bool[0x10];
  35. foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
  36. {
  37. Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
  38. if (!nca.IsProgram() && nca.IsPatch())
  39. {
  40. continue;
  41. }
  42. ulong currentProgramId = nca.Header.TitleId;
  43. ulong currentMainProgramId = currentProgramId & ~0xFFFul;
  44. if (applicationId == 0 && currentMainProgramId != 0)
  45. {
  46. applicationId = currentMainProgramId;
  47. }
  48. if (applicationId != currentMainProgramId)
  49. {
  50. // Currently there aren't any known multi-application game cards containing multi-program applications,
  51. // so because multi-application game cards are the only way we could run into multiple applications
  52. // we'll just return that there's a single program.
  53. programCount = 1;
  54. break;
  55. }
  56. hasIndex[(int)(currentProgramId & 0xF)] = true;
  57. }
  58. if (programCount == 0)
  59. {
  60. for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
  61. {
  62. programCount++;
  63. }
  64. }
  65. if (programCount <= 0)
  66. {
  67. return LibHac.Result.Success;
  68. }
  69. Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
  70. for (int i = 0; i < programCount; i++)
  71. {
  72. mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
  73. mapInfo[i].MainProgramId = new ApplicationId(applicationId);
  74. mapInfo[i].ProgramIndex = (byte)i;
  75. }
  76. return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]);
  77. }
  78. public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct<ApplicationControlProperty> applicationControlProperty)
  79. {
  80. Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
  81. ref ApplicationControlProperty control = ref applicationControlProperty.Value;
  82. if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan))
  83. {
  84. // If the current application doesn't have a loaded control property, create a dummy one and set the savedata sizes so a user savedata will be created.
  85. control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
  86. // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
  87. control.UserAccountSaveDataSize = 0x4000;
  88. control.UserAccountSaveDataJournalSize = 0x4000;
  89. control.SaveDataOwnerId = applicationId.Value;
  90. Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
  91. }
  92. LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
  93. if (resultCode.IsFailure())
  94. {
  95. Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
  96. return resultCode;
  97. }
  98. Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
  99. resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId);
  100. if (resultCode.IsFailure())
  101. {
  102. Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
  103. }
  104. return resultCode;
  105. }
  106. public static bool LoadKip(KernelContext context, KipExecutable kip)
  107. {
  108. uint endOffset = kip.DataOffset + (uint)kip.Data.Length;
  109. if (kip.BssSize != 0)
  110. {
  111. endOffset = kip.BssOffset + kip.BssSize;
  112. }
  113. uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize);
  114. int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
  115. ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
  116. ulong codeAddress = codeBaseAddress + kip.TextOffset;
  117. ProcessCreationFlags flags = 0;
  118. if (ProcessConst.AslrEnabled)
  119. {
  120. // TODO: Randomization.
  121. flags |= ProcessCreationFlags.EnableAslr;
  122. }
  123. if (kip.Is64BitAddressSpace)
  124. {
  125. flags |= ProcessCreationFlags.AddressSpace64Bit;
  126. }
  127. if (kip.Is64Bit)
  128. {
  129. flags |= ProcessCreationFlags.Is64Bit;
  130. }
  131. ProcessCreationInfo creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0);
  132. MemoryRegion memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application;
  133. KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
  134. Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount);
  135. if (result != Result.Success)
  136. {
  137. Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
  138. return false;
  139. }
  140. KProcess process = new(context);
  141. var processContextFactory = new ArmProcessContextFactory(
  142. context.Device.System.TickSource,
  143. context.Device.Gpu,
  144. string.Empty,
  145. string.Empty,
  146. false,
  147. codeAddress,
  148. codeSize);
  149. result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory);
  150. if (result != Result.Success)
  151. {
  152. Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
  153. return false;
  154. }
  155. result = LoadIntoMemory(process, kip, codeBaseAddress);
  156. if (result != Result.Success)
  157. {
  158. Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
  159. return false;
  160. }
  161. process.DefaultCpuCore = kip.IdealCoreId;
  162. result = process.Start(kip.Priority, (ulong)kip.StackSize);
  163. if (result != Result.Success)
  164. {
  165. Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
  166. return false;
  167. }
  168. context.Processes.TryAdd(process.Pid, process);
  169. return true;
  170. }
  171. public static ProcessResult LoadNsos(
  172. Switch device,
  173. KernelContext context,
  174. MetaLoader metaLoader,
  175. ApplicationControlProperty applicationControlProperties,
  176. bool diskCacheEnabled,
  177. bool allowCodeMemoryForJit,
  178. string name,
  179. ulong programId,
  180. byte[] arguments = null,
  181. params IExecutable[] executables)
  182. {
  183. context.Device.System.ServiceTable.WaitServicesReady();
  184. LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm);
  185. if (resultCode.IsFailure())
  186. {
  187. Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}");
  188. return ProcessResult.Failed;
  189. }
  190. ref readonly var meta = ref npdm.Meta;
  191. ulong argsStart = 0;
  192. uint argsSize = 0;
  193. ulong codeStart = (meta.Flags & 1) != 0 ? 0x8000000UL : 0x200000UL;
  194. uint codeSize = 0;
  195. var buildIds = executables.Select(e => (e switch
  196. {
  197. NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()),
  198. NroExecutable nro => Convert.ToHexString(nro.Header.BuildId),
  199. _ => ""
  200. }).ToUpper());
  201. ulong[] nsoBase = new ulong[executables.Length];
  202. for (int index = 0; index < executables.Length; index++)
  203. {
  204. IExecutable nso = executables[index];
  205. uint textEnd = nso.TextOffset + (uint)nso.Text.Length;
  206. uint roEnd = nso.RoOffset + (uint)nso.Ro.Length;
  207. uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize;
  208. uint nsoSize = textEnd;
  209. if (nsoSize < roEnd)
  210. {
  211. nsoSize = roEnd;
  212. }
  213. if (nsoSize < dataEnd)
  214. {
  215. nsoSize = dataEnd;
  216. }
  217. nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
  218. nsoBase[index] = codeStart + codeSize;
  219. codeSize += nsoSize;
  220. if (arguments != null && argsSize == 0)
  221. {
  222. argsStart = codeSize;
  223. argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize);
  224. codeSize += argsSize;
  225. }
  226. }
  227. int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
  228. int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
  229. ProcessCreationInfo creationInfo = new(
  230. name,
  231. (int)meta.Version,
  232. programId,
  233. codeStart,
  234. codePagesCount,
  235. (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
  236. 0,
  237. personalMmHeapPagesCount);
  238. context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm);
  239. Result result;
  240. KResourceLimit resourceLimit = new(context);
  241. long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size;
  242. result = resourceLimit.SetLimitValue(LimitableResource.Memory, applicationRgSize);
  243. if (result.IsSuccess)
  244. {
  245. result = resourceLimit.SetLimitValue(LimitableResource.Thread, 608);
  246. }
  247. if (result.IsSuccess)
  248. {
  249. result = resourceLimit.SetLimitValue(LimitableResource.Event, 700);
  250. }
  251. if (result.IsSuccess)
  252. {
  253. result = resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 128);
  254. }
  255. if (result.IsSuccess)
  256. {
  257. result = resourceLimit.SetLimitValue(LimitableResource.Session, 894);
  258. }
  259. if (result != Result.Success)
  260. {
  261. Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values.");
  262. return ProcessResult.Failed;
  263. }
  264. KProcess process = new(context, allowCodeMemoryForJit);
  265. // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found.
  266. MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf);
  267. if (memoryRegion > MemoryRegion.NvServices)
  268. {
  269. Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags.");
  270. return ProcessResult.Failed;
  271. }
  272. var processContextFactory = new ArmProcessContextFactory(
  273. context.Device.System.TickSource,
  274. context.Device.Gpu,
  275. $"{programId:x16}",
  276. applicationControlProperties.DisplayVersionString.ToString(),
  277. diskCacheEnabled,
  278. codeStart,
  279. codeSize);
  280. result = process.Initialize(
  281. creationInfo,
  282. MemoryMarshal.Cast<byte, uint>(npdm.KernelCapabilityData),
  283. resourceLimit,
  284. memoryRegion,
  285. processContextFactory);
  286. if (result != Result.Success)
  287. {
  288. Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
  289. return ProcessResult.Failed;
  290. }
  291. for (int index = 0; index < executables.Length; index++)
  292. {
  293. Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
  294. result = LoadIntoMemory(process, executables[index], nsoBase[index]);
  295. if (result != Result.Success)
  296. {
  297. Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
  298. return ProcessResult.Failed;
  299. }
  300. }
  301. process.DefaultCpuCore = meta.DefaultCpuId;
  302. context.Processes.TryAdd(process.Pid, process);
  303. // Keep the build ids because the tamper machine uses them to know which process to associate a
  304. // tamper to and also keep the starting address of each executable inside a process because some
  305. // memory modifications are relative to this address.
  306. ProcessTamperInfo tamperInfo = new(
  307. process,
  308. buildIds,
  309. nsoBase,
  310. process.MemoryManager.HeapRegionStart,
  311. process.MemoryManager.AliasRegionStart,
  312. process.MemoryManager.CodeRegionStart);
  313. // Once everything is loaded, we can load cheats.
  314. device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine);
  315. return new ProcessResult(metaLoader, applicationControlProperties, diskCacheEnabled, allowCodeMemoryForJit, processContextFactory.DiskCacheLoadState, process.Pid, meta.MainThreadPriority, meta.MainThreadStackSize);
  316. }
  317. public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
  318. {
  319. ulong textStart = baseAddress + image.TextOffset;
  320. ulong roStart = baseAddress + image.RoOffset;
  321. ulong dataStart = baseAddress + image.DataOffset;
  322. ulong bssStart = baseAddress + image.BssOffset;
  323. ulong end = dataStart + (ulong)image.Data.Length;
  324. if (image.BssSize != 0)
  325. {
  326. end = bssStart + image.BssSize;
  327. }
  328. process.CpuMemory.Write(textStart, image.Text);
  329. process.CpuMemory.Write(roStart, image.Ro);
  330. process.CpuMemory.Write(dataStart, image.Data);
  331. process.CpuMemory.Fill(bssStart, image.BssSize, 0);
  332. Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
  333. {
  334. if (size == 0)
  335. {
  336. return Result.Success;
  337. }
  338. size = BitUtils.AlignUp<ulong>(size, KPageTableBase.PageSize);
  339. return process.MemoryManager.SetProcessMemoryPermission(address, size, permission);
  340. }
  341. Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute);
  342. if (result != Result.Success)
  343. {
  344. return result;
  345. }
  346. result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read);
  347. if (result != Result.Success)
  348. {
  349. return result;
  350. }
  351. return SetProcessMemoryPermission(dataStart, end - dataStart, KMemoryPermission.ReadAndWrite);
  352. }
  353. }
  354. }