/ src / Ryujinx.HLE / Loaders / Processes / ProcessLoader.cs
ProcessLoader.cs
  1  using LibHac.Common;
  2  using LibHac.Fs;
  3  using LibHac.Fs.Fsa;
  4  using LibHac.FsSystem;
  5  using LibHac.Ns;
  6  using LibHac.Tools.Fs;
  7  using LibHac.Tools.FsSystem;
  8  using LibHac.Tools.FsSystem.NcaUtils;
  9  using Ryujinx.Common.Logging;
 10  using Ryujinx.HLE.Loaders.Executables;
 11  using Ryujinx.HLE.Loaders.Processes.Extensions;
 12  using System;
 13  using System.Collections.Concurrent;
 14  using System.IO;
 15  using Path = System.IO.Path;
 16  
 17  namespace Ryujinx.HLE.Loaders.Processes
 18  {
 19      public class ProcessLoader
 20      {
 21          private readonly Switch _device;
 22  
 23          private readonly ConcurrentDictionary<ulong, ProcessResult> _processesByPid;
 24  
 25          private ulong _latestPid;
 26  
 27          public ProcessResult ActiveApplication => _processesByPid[_latestPid];
 28  
 29          public ProcessLoader(Switch device)
 30          {
 31              _device = device;
 32              _processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
 33          }
 34  
 35          public bool LoadXci(string path, ulong applicationId)
 36          {
 37              FileStream stream = new(path, FileMode.Open, FileAccess.Read);
 38              Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
 39  
 40              if (!xci.HasPartition(XciPartitionType.Secure))
 41              {
 42                  Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition");
 43  
 44                  return false;
 45              }
 46  
 47              (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage);
 48  
 49              if (!success)
 50              {
 51                  Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
 52  
 53                  return false;
 54              }
 55  
 56              if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
 57              {
 58                  if (processResult.Start(_device))
 59                  {
 60                      _latestPid = processResult.ProcessId;
 61  
 62                      return true;
 63                  }
 64              }
 65  
 66              return false;
 67          }
 68  
 69          public bool LoadNsp(string path, ulong applicationId)
 70          {
 71              FileStream file = new(path, FileMode.Open, FileAccess.Read);
 72              PartitionFileSystem partitionFileSystem = new();
 73              partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
 74  
 75              (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage);
 76  
 77              if (processResult.ProcessId == 0)
 78              {
 79                  // This is not a normal NSP, it's actually a ExeFS as a NSP
 80                  processResult = partitionFileSystem.Load(_device, new BlitStruct<ApplicationControlProperty>(1), partitionFileSystem.GetNpdm(), 0, true);
 81              }
 82  
 83              if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
 84              {
 85                  if (processResult.Start(_device))
 86                  {
 87                      _latestPid = processResult.ProcessId;
 88  
 89                      return true;
 90                  }
 91              }
 92  
 93              if (!success)
 94              {
 95                  Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad));
 96              }
 97  
 98              return false;
 99          }
100  
101          public bool LoadNca(string path)
102          {
103              FileStream file = new(path, FileMode.Open, FileAccess.Read);
104              Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
105  
106              ProcessResult processResult = nca.Load(_device, null, null);
107  
108              if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
109              {
110                  if (processResult.Start(_device))
111                  {
112                      // NOTE: Check if process is SystemApplicationId or ApplicationId
113                      if (processResult.ProgramId > 0x01000000000007FF)
114                      {
115                          _latestPid = processResult.ProcessId;
116                      }
117  
118                      return true;
119                  }
120              }
121  
122              return false;
123          }
124  
125          public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null)
126          {
127              ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
128  
129              if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult))
130              {
131                  if (processResult.Start(_device))
132                  {
133                      _latestPid = processResult.ProcessId;
134  
135                      return true;
136                  }
137              }
138  
139              return false;
140          }
141  
142          public bool LoadNxo(string path)
143          {
144              var nacpData = new BlitStruct<ApplicationControlProperty>(1);
145              IFileSystem dummyExeFs = null;
146              Stream romfsStream = null;
147  
148              string programName = "";
149              ulong programId = 0000000000000000;
150  
151              // Load executable.
152              IExecutable executable;
153  
154              if (Path.GetExtension(path).ToLower() == ".nro")
155              {
156                  FileStream input = new(path, FileMode.Open);
157                  NroExecutable nro = new(input.AsStorage());
158  
159                  executable = nro;
160  
161                  // Open RomFS if exists.
162                  IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false);
163                  romFsStorage.GetSize(out long romFsSize).ThrowIfFailure();
164                  if (romFsSize != 0)
165                  {
166                      romfsStream = romFsStorage.AsStream();
167                  }
168  
169                  // Load Nacp if exists.
170                  IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false);
171                  nacpStorage.GetSize(out long nacpSize).ThrowIfFailure();
172                  if (nacpSize != 0)
173                  {
174                      nacpStorage.Read(0, nacpData.ByteSpan);
175  
176                      programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
177  
178                      if (string.IsNullOrWhiteSpace(programName))
179                      {
180                          programName = Array.Find(nacpData.Value.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString();
181                      }
182  
183                      if (nacpData.Value.PresenceGroupId != 0)
184                      {
185                          programId = nacpData.Value.PresenceGroupId;
186                      }
187                      else if (nacpData.Value.SaveDataOwnerId != 0)
188                      {
189                          programId = nacpData.Value.SaveDataOwnerId;
190                      }
191                      else if (nacpData.Value.AddOnContentBaseId != 0)
192                      {
193                          programId = nacpData.Value.AddOnContentBaseId - 0x1000;
194                      }
195                  }
196  
197                  // TODO: Add icon maybe ?
198              }
199              else
200              {
201                  programName = Path.GetFileNameWithoutExtension(path);
202  
203                  executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName);
204              }
205  
206              // Explicitly null TitleId to disable the shader cache.
207              Graphics.Gpu.GraphicsConfig.TitleId = null;
208              _device.Gpu.HostInitalized.Set();
209  
210              ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
211                                                                         _device.System.KernelContext,
212                                                                         dummyExeFs.GetNpdm(),
213                                                                         nacpData,
214                                                                         diskCacheEnabled: false,
215                                                                         allowCodeMemoryForJit: true,
216                                                                         programName,
217                                                                         programId,
218                                                                         0,
219                                                                         null,
220                                                                         executable);
221  
222              // Make sure the process id is valid.
223              if (processResult.ProcessId != 0)
224              {
225                  // Load RomFS.
226                  if (romfsStream != null)
227                  {
228                      _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream);
229                  }
230  
231                  // Start process.
232                  if (_processesByPid.TryAdd(processResult.ProcessId, processResult))
233                  {
234                      if (processResult.Start(_device))
235                      {
236                          _latestPid = processResult.ProcessId;
237  
238                          return true;
239                      }
240                  }
241              }
242  
243              return false;
244          }
245      }
246  }