From 70f651471665fd5f6fd6c7c872e255dec8b93b11 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 3 Sep 2018 21:09:48 -0500 Subject: [PATCH] Add XCI and NCA loading support --- Ryujinx.HLE/HOS/Horizon.cs | 147 +++++++++++++++++++++++++++++++ Ryujinx.HLE/Ryujinx.HLE.csproj | 1 + Ryujinx.HLE/Switch.cs | 10 +++ Ryujinx.HLE/VirtualFileSystem.cs | 6 ++ Ryujinx/Ui/Program.cs | 18 +++- 5 files changed, 179 insertions(+), 3 deletions(-) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index a557967523..86c9fbf693 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -7,6 +7,8 @@ using Ryujinx.HLE.Logging; using System; using System.Collections.Concurrent; using System.IO; +using System.Linq; +using LibHac; namespace Ryujinx.HLE.HOS { @@ -30,6 +32,8 @@ namespace Ryujinx.HLE.HOS internal KEvent VsyncEvent { get; private set; } + internal Keyset Keyset { get; private set; } + public Horizon(Switch Device) { this.Device = Device; @@ -52,6 +56,8 @@ namespace Ryujinx.HLE.HOS Font = new SharedFontManager(Device, FontSharedMem.PA); VsyncEvent = new KEvent(); + + LoadDefaultKeyset(); } public void LoadCart(string ExeFsDir, string RomFsFile = null) @@ -119,6 +125,118 @@ namespace Ryujinx.HLE.HOS MainProcess.Run(); } + public void LoadXci(string xciFile) + { + var file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); + var xci = new Xci(Keyset, file); + var nca = GetXciMainNca(xci); + LoadNca(nca); + } + + private Nca GetXciMainNca(Xci xci) + { + if (xci.SecurePartition == null) + { + Device.Log.PrintError(LogClass.Loader, "Could not find XCI secure partition"); + return null; + } + + Nca mainNca = null; + + foreach (var fileEntry in xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca"))) + { + var ncaStream = xci.SecurePartition.OpenFile(fileEntry); + var nca = new Nca(Keyset, ncaStream, true); + + if (nca.Header.ContentType == ContentType.Program) + { + mainNca = nca; + } + } + + return mainNca; + } + + public void LoadNca(string ncaFile) + { + var file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); + var nca = new Nca(Keyset, file, true); + LoadNca(nca); + } + + public void LoadNca(Nca nca) + { + NcaSection romfsSection = nca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs); + NcaSection exefsSection = nca.Sections.FirstOrDefault(x => x.IsExefs); + + if (exefsSection == null) + { + Device.Log.PrintError(LogClass.Loader, "No ExeFS found in NCA"); + return; + } + + if (romfsSection == null) + { + Device.Log.PrintError(LogClass.Loader, "No RomFS found in NCA"); + return; + } + + var romfsStream = nca.OpenSection(romfsSection.SectionNum, false); + Device.FileSystem.SetRomFs(romfsStream); + + var exefsStream = nca.OpenSection(exefsSection.SectionNum, false); + var exefs = new Pfs(exefsStream); + + Npdm MetaData = null; + + if (exefs.FileExists("main.npdm")) + { + Device.Log.PrintInfo(LogClass.Loader, "Loading main.npdm..."); + MetaData = new Npdm(exefs.OpenFile("main.npdm")); + + } + else + { + Device.Log.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); + } + + Process MainProcess = MakeProcess(MetaData); + + void LoadNso(string filename) + { + foreach (var File in exefs.Files.Where(x => x.Name.StartsWith(filename))) + { + if (Path.GetExtension(File.Name) != string.Empty) + { + continue; + } + + Device.Log.PrintInfo(LogClass.Loader, $"Loading {filename}..."); + + string Name = Path.GetFileNameWithoutExtension(File.Name); + + Nso Program = new Nso(exefs.OpenFile(File), Name); + + MainProcess.LoadProgram(Program); + } + } + + if (!MainProcess.MetaData.Is64Bits) + { + throw new NotImplementedException("32-bit titles are unsupported!"); + } + + LoadNso("rtld"); + + MainProcess.SetEmptyArgs(); + + LoadNso("main"); + LoadNso("subsdk"); + LoadNso("sdk"); + + MainProcess.Run(); + } + public void LoadProgram(string FilePath) { bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro"; @@ -156,6 +274,35 @@ namespace Ryujinx.HLE.HOS MainProcess.Run(IsNro); } + public void LoadDefaultKeyset() + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var homeKeyFile = Path.Combine(home, ".switch", "prod.keys"); + var homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys"); + var homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys"); + + string keyFile = null; + string titleKeyFile = null; + string consoleKeyFile = null; + + if (File.Exists(homeKeyFile)) + { + keyFile = homeKeyFile; + } + + if (File.Exists(homeTitleKeyFile)) + { + titleKeyFile = homeTitleKeyFile; + } + + if (File.Exists(homeConsoleKeyFile)) + { + consoleKeyFile = homeConsoleKeyFile; + } + + Keyset = ExternalKeys.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); + } + public void SignalVsync() => VsyncEvent.WaitEvent.Set(); private Process MakeProcess(Npdm MetaData = null) diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index fa4c254e24..cd1bb03458 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -25,6 +25,7 @@ + diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 03dc44e851..2b9ca4fbb2 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -61,6 +61,16 @@ namespace Ryujinx.HLE System.LoadCart(ExeFsDir, RomFsFile); } + public void LoadXci(string xciFile) + { + System.LoadXci(xciFile); + } + + public void LoadNca(string ncaFile) + { + System.LoadNca(ncaFile); + } + public void LoadProgram(string FileName) { System.LoadProgram(FileName); diff --git a/Ryujinx.HLE/VirtualFileSystem.cs b/Ryujinx.HLE/VirtualFileSystem.cs index 31b8e184ce..1c2d0b1e58 100644 --- a/Ryujinx.HLE/VirtualFileSystem.cs +++ b/Ryujinx.HLE/VirtualFileSystem.cs @@ -17,6 +17,12 @@ namespace Ryujinx.HLE RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read); } + public void SetRomFs(Stream romfsStream) + { + RomFs?.Close(); + RomFs = romfsStream; + } + public string GetFullPath(string BasePath, string FileName) { if (FileName.StartsWith("//")) diff --git a/Ryujinx/Ui/Program.cs b/Ryujinx/Ui/Program.cs index fdbc59a5a8..7f3c0ae99b 100644 --- a/Ryujinx/Ui/Program.cs +++ b/Ryujinx/Ui/Program.cs @@ -50,9 +50,21 @@ namespace Ryujinx } else if (File.Exists(args[0])) { - Console.WriteLine("Loading as homebrew."); - - Device.LoadProgram(args[0]); + switch (Path.GetExtension(args[0])) + { + case ".xci": + Console.WriteLine("Loading as XCI."); + Device.LoadXci(args[0]); + break; + case ".nca": + Console.WriteLine("Loading as NCA."); + Device.LoadNca(args[0]); + break; + default: + Console.WriteLine("Loading as homebrew."); + Device.LoadProgram(args[0]); + break; + } } } else