ProcessLoaderHelper.cs 18 KB

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