From 535badd91e5397eb890bc2a0d24dd0abf1c352e8 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Mon, 14 Oct 2019 15:49:40 +0000 Subject: [PATCH] firmware installer --- .../FileSystem/Content/ContentManager.cs | 550 +++++++++++++++++- .../FileSystem/Content/SystemVersion.cs | 41 ++ Ryujinx.HLE/HOS/Font/SharedFontManager.cs | 8 + Ryujinx.HLE/HOS/Horizon.cs | 15 + Ryujinx/Ui/MainWindow.cs | 193 +++++- Ryujinx/Ui/MainWindow.glade | 15 + 6 files changed, 807 insertions(+), 15 deletions(-) create mode 100644 Ryujinx.HLE/FileSystem/Content/SystemVersion.cs diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index 17faa19f62..1c061dffbb 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -1,19 +1,27 @@ -using LibHac.FsSystem; +using LibHac; +using LibHac.Fs; +using LibHac.FsService; +using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using Ryujinx.HLE.HOS.Font; using Ryujinx.HLE.HOS.Services.Time; using Ryujinx.HLE.Utilities; using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; +using static Ryujinx.Common.HexUtils; + namespace Ryujinx.HLE.FileSystem.Content { internal class ContentManager { private Dictionary> _locationEntries; - private Dictionary _sharedFontTitleDictionary; + private Dictionary _sharedFontTitleDictionary; private Dictionary _sharedFontFilenameDictionary; private SortedDictionary<(ulong, NcaContentType), string> _contentDictionary; @@ -51,6 +59,7 @@ namespace Ryujinx.HLE.FileSystem.Content public void LoadEntries() { _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); foreach (StorageId storageId in Enum.GetValues(typeof(StorageId))) { @@ -144,6 +153,8 @@ namespace Ryujinx.HLE.FileSystem.Content } TimeManager.Instance.InitializeTimeZone(_device); + + _device.System.Font.Initialize(this); } public void ClearEntry(long titleId, NcaContentType contentType, StorageId storageId) @@ -153,7 +164,7 @@ namespace Ryujinx.HLE.FileSystem.Content public void RefreshEntries(StorageId storageId, int flag) { - LinkedList locationList = _locationEntries[storageId]; + LinkedList locationList = _locationEntries[storageId]; LinkedListNode locationEntry = locationList.First; while (locationEntry != null) @@ -173,10 +184,11 @@ namespace Ryujinx.HLE.FileSystem.Content { if (_contentDictionary.ContainsValue(ncaId)) { - var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); - long titleId = (long)content.Key.Item1; + var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); + long titleId = (long)content.Key.Item1; + NcaContentType contentType = content.Key.Item2; - StorageId storage = GetInstalledStorage(titleId, contentType, storageId); + StorageId storage = GetInstalledStorage(titleId, contentType, storageId); return storage == storageId; } @@ -186,9 +198,9 @@ namespace Ryujinx.HLE.FileSystem.Content public UInt128 GetInstalledNcaId(long titleId, NcaContentType contentType) { - if (_contentDictionary.ContainsKey(((ulong)titleId,contentType))) + if (_contentDictionary.ContainsKey(((ulong)titleId, contentType))) { - return new UInt128(_contentDictionary[((ulong)titleId,contentType)]); + return new UInt128(_contentDictionary[((ulong)titleId, contentType)]); } return new UInt128(); @@ -232,9 +244,8 @@ namespace Ryujinx.HLE.FileSystem.Content { return false; } - - StorageId storageId = LocationHelper.GetStorageId(locationEntry.ContentPath); - string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); + + string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); if (!string.IsNullOrWhiteSpace(installedPath)) { @@ -242,7 +253,7 @@ namespace Ryujinx.HLE.FileSystem.Content { using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read)) { - Nca nca = new Nca(_device.System.KeySet, file.AsStorage()); + Nca nca = new Nca(_device.System.KeySet, file.AsStorage()); bool contentCheck = nca.Header.ContentType == contentType; return contentCheck; @@ -310,5 +321,520 @@ namespace Ryujinx.HLE.FileSystem.Content return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); } + + public void InstallFirmware(string firmwareSource) + { + string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem); + string contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); + string registeredDirectory = Path.Combine(contentDirectory, "registered"); + string temporalDirectory = Path.Combine(contentDirectory, "temp"); + + if (Directory.Exists(temporalDirectory)) + { + new DirectoryInfo(temporalDirectory).Delete(true); + } + + if (Directory.Exists(firmwareSource)) + { + InstallFromDirectory(firmwareSource, temporalDirectory); + FinishInstallation(temporalDirectory, registeredDirectory); + return; + } + + if (!File.Exists(firmwareSource)) + throw new FileNotFoundException("Firmware file does not exist."); + + FileInfo info = new FileInfo(firmwareSource); + + using (FileStream file = File.OpenRead(firmwareSource)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwareSource)) + { + InstallFromZip(archive, temporalDirectory); + } + break; + case ".xci": + Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); + InstallFromCart(xci, temporalDirectory); + break; + default: + throw new FormatException("Input file is not a valid firmware package"); + } + + FinishInstallation(temporalDirectory, registeredDirectory); + } + } + + private void FinishInstallation(string temporalDirectory, string registeredDirectory) + { + if (Directory.Exists(registeredDirectory)) + { + new DirectoryInfo(registeredDirectory).Delete(true); + } + + Directory.Move(temporalDirectory, registeredDirectory); + + LoadEntries(); + } + + private void InstallFromDirectory(string firmwareDirectory, string temporalDirectory) + { + InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporalDirectory); + } + + private void InstallFromPartition(IFileSystem filesystem, string temporalDirectory) + { + foreach (var entry in filesystem.EnumerateEntries("./", "*.nca")) + { + Nca nca = new Nca(_device.System.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); + + SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporalDirectory); + } + } + + private void InstallFromCart(Xci gameCard, string temporalDirectory) + { + if (gameCard.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); + + InstallFromPartition(partition, temporalDirectory); + } + else + throw new Exception("Update not found in xci file."); + } + + private void InstallFromZip(ZipArchive archive, string temporalDirectory) + { + using (archive) + { + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + // Clean up the name and get the NcaId + + string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); + + string ncaId = pathComponents[pathComponents.Length - 1]; + + // If this is a fragmented nca, we need to get the previous element.GetZip + if (ncaId.Equals("00")) + { + ncaId = pathComponents[pathComponents.Length - 2]; + } + + if (ncaId.Contains(".nca")) + { + string newPath = Path.Combine(temporalDirectory, ncaId); + + Directory.CreateDirectory(newPath); + + entry.ExtractToFile(Path.Combine(newPath, "00")); + } + } + } + } + } + + public void SaveNca(Nca nca, string ncaId, string temporalDirectory) + { + string newPath = Path.Combine(temporalDirectory, ncaId + ".nca"); + + Directory.CreateDirectory(newPath); + + using (FileStream file = File.Create(Path.Combine(newPath, "00"))) + { + nca.BaseStorage.AsStream().CopyTo(file); + } + } + + private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode) + { + IFile file; + + if (filesystem.FileExists($"{path}/00")) + { + filesystem.OpenFile(out file, $"{path}/00", mode); + } + else + filesystem.OpenFile(out file, path, mode); + + return file; + } + + private Stream GetZipStream(ZipArchiveEntry entry) + { + MemoryStream dest = new MemoryStream(); + + Stream src = entry.Open(); + + src.CopyTo(dest); + src.Dispose(); + + return dest; + } + + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + ulong systemVersionTitleId = 0x0100000000000809; + ulong systemUpdateTitleId = 0x0100000000000816; + + Dictionary> updateNcas = new Dictionary>(); + + if (Directory.Exists(firmwarePackage)) + return VerifyAndGetVersionDirectory(firmwarePackage); + + if (!File.Exists(firmwarePackage)) + throw new FileNotFoundException("Firmware file does not exist."); + + FileInfo info = new FileInfo(firmwarePackage); + + using (FileStream file = File.OpenRead(firmwarePackage)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) + { + return VerifyAndGetVersionZip(archive); + } + case ".xci": + Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); + + if (xci.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = xci.OpenPartition(XciPartitionType.Update); + + return VerifyAndGetVersion(partition); + } + else + throw new InvalidDataException("Update not found in xci file."); + default: + break; + } + } + + SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) + { + return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); + } + + SystemVersion VerifyAndGetVersionZip(ZipArchive archive) + { + SystemVersion systemVersion = null; + + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + using (Stream ncaStream = GetZipStream(entry)) + { + IStorage storage = ncaStream.AsStorage(); + + Nca nca = new Nca(_device.System.KeySet, storage); + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + } + } + } + + if (updateNcas.ContainsKey(systemUpdateTitleId)) + { + var ncaEntry = updateNcas[systemUpdateTitleId]; + + string metaPath = ncaEntry.Find(x => x.Item1 == NcaContentType.Meta).Item2; + + CnmtContentMetaEntry[] metaEntries = null; + + var fileEntry = archive.GetEntry(metaPath); + + using (Stream ncaStream = GetZipStream(fileEntry)) + { + Nca metaNca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("./", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read) == Result.Success) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + + updateNcas.Remove(systemUpdateTitleId); + }; + } + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + if (updateNcas.ContainsKey(systemVersionTitleId)) + { + string versionEntry = updateNcas[systemVersionTitleId].Find(x => x.Item1 != NcaContentType.Meta).Item2; + + using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry))) + { + Nca nca = new Nca(_device.System.KeySet, ncaStream.AsStorage()); + + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read) == Result.Success) + { + systemVersion = new SystemVersion(systemVersionFile.AsStream()); + } + } + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) + { + metaPath = ncaEntry.Find(x => x.Item1 == NcaContentType.Meta).Item2; + string contentPath = ncaEntry.Find(x => x.Item1 != NcaContentType.Meta).Item2; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + continue; + } + + ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); + ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); + + using (Stream metaNcaStream = GetZipStream(metaZipEntry)) + { + using (Stream contentNcaStream = GetZipStream(contentZipEntry)) + { + Nca metaNca = new Nca(_device.System.KeySet, metaNcaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("./", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read) == Result.Success) + { + + var meta = new Cnmt(metaFile.AsStream()); + + IStorage contentStorage = contentNcaStream.AsStorage(); + if (contentStorage.GetSize(out long size) == Result.Success) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + + } + } + } + + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var nca in entry.Value) + { + extraNcas += nca.Item2 + Environment.NewLine; + } + } + + throw new InvalidDataException($"Firmware package contains unrelated archives. Please remove these paths: \n{extraNcas}"); + } + } + else + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + return systemVersion; + } + + SystemVersion VerifyAndGetVersion(IFileSystem filesystem) + { + SystemVersion systemVersion = null; + + CnmtContentMetaEntry[] metaEntries = null; + + foreach (var entry in filesystem.EnumerateEntries("./", "*.nca")) + { + IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); + + Nca nca = new Nca(_device.System.KeySet, ncaStorage); + + if (nca.Header.TitleId == systemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) + { + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("./", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read) == Result.Success) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + } + }; + + continue; + } + else if (nca.Header.TitleId == systemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read) == Result.Success) + { + systemVersion = new SystemVersion(systemVersionFile.AsStream()); + } + } + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + + ncaStorage.Dispose(); + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry)) + { + var metaNcaEntry = ncaEntry.Find(x => x.Item1 == NcaContentType.Meta); + var contentNcaEntry = ncaEntry.Find(x => x.Item1 != NcaContentType.Meta); + + IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.Item2, OpenMode.Read).AsStorage(); + IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentNcaEntry.Item2, OpenMode.Read).AsStorage(); + + Nca metaNca = new Nca(_device.System.KeySet, metaStorage); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + string cnmtPath = fs.EnumerateEntries("./", "*.cnmt").Single().FullPath; + + if (fs.OpenFile(out IFile metaFile, cnmtPath, OpenMode.Read) == Result.Success) + { + + var meta = new Cnmt(metaFile.AsStream()); + + if (contentStorage.GetSize(out long size) == Result.Success) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var nca in entry.Value) + { + extraNcas += nca.Item2 + Environment.NewLine; + } + } + + throw new InvalidDataException($"Firmware package contains unrelated archives. Please remove these paths: \n{extraNcas}"); + } + + return systemVersion; + } + + return null; + } + + public SystemVersion GetCurrentFirmwareVersion() + { + LoadEntries(); + + var locationEnties = _locationEntries[StorageId.NandSystem]; + + foreach (var entry in locationEnties) + { + if (entry.ContentType == NcaContentType.Data) + { + var path = _device.FileSystem.SwitchPathToSystemPath(entry.ContentPath); + + using (IStorage ncaStorage = File.Open(path, FileMode.Open).AsStorage()) + { + Nca nca = new Nca(_device.System.KeySet, ncaStorage); + + if (nca.Header.TitleId == 0x0100000000000809 && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read) == Result.Success) + { + return new SystemVersion(systemVersionFile.AsStream()); + } + } + + } + } + } + + return null; + } } } diff --git a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs new file mode 100644 index 0000000000..ed62a497d7 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.FileSystem.Content +{ + public class SystemVersion + { + public byte Major { get; set; } + public byte Minor { get; set; } + public byte Micro { get; set; } + public byte RevisionMajor { get; set; } + public byte RevisionMinor { get; set; } + public string PlatformString { get; set; } + public string Hex { get; set; } + public string VersionString { get; set; } + public string VersionTitle { get; set; } + + public SystemVersion(Stream systemVersionFile) + { + using(BinaryReader reader = new BinaryReader(systemVersionFile)) + { + Major = reader.ReadByte(); + Minor = reader.ReadByte(); + Micro = reader.ReadByte(); + + reader.ReadByte(); // Padding + + RevisionMajor = reader.ReadByte(); + RevisionMinor = reader.ReadByte(); + + reader.ReadBytes(2); // Padding + + PlatformString = Encoding.ASCII.GetString(reader.ReadBytes(0x20)).TrimEnd('\0'); + Hex = Encoding.ASCII.GetString(reader.ReadBytes(0x40)).TrimEnd('\0'); + VersionString = Encoding.ASCII.GetString(reader.ReadBytes(0x18)).TrimEnd('\0'); + VersionTitle = Encoding.ASCII.GetString(reader.ReadBytes(0x80)).TrimEnd('\0'); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs index 99b662c0d1..c54c8194bc 100644 --- a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs @@ -44,6 +44,14 @@ namespace Ryujinx.HLE.HOS.Font _fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts"); } + public void Initialize(ContentManager contentManager) + { + _fontData?.Clear(); + _fontData = null; + + EnsureInitialized(contentManager); + } + public void EnsureInitialized(ContentManager contentManager) { if (_fontData == null) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 164a49a002..855d89148c 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -715,6 +715,21 @@ namespace Ryujinx.HLE.HOS } } + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + return ContentManager.VerifyFirmwarePackage(firmwarePackage); + } + + public SystemVersion GetCurrentFirmwareVersion() + { + return ContentManager.GetCurrentFirmwareVersion(); + } + + public void InstallFirmware(string firmwarePackage) + { + ContentManager.InstallFirmware(firmwarePackage); + } + public void SignalVsync() { VsyncEvent.ReadableEvent.Signal(); diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index e65e56ffd5..8eb41461f8 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -45,6 +45,7 @@ namespace Ryujinx.Ui [GUI] CheckMenuItem _fullScreen; [GUI] MenuItem _stopEmulation; [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstall; [GUI] CheckMenuItem _iconToggle; [GUI] CheckMenuItem _appToggle; [GUI] CheckMenuItem _developerToggle; @@ -295,8 +296,9 @@ namespace Ryujinx.Ui new Thread(CreateGameWindow).Start(); #endif - _gameLoaded = true; - _stopEmulation.Sensitive = true; + _gameLoaded = true; + _stopEmulation.Sensitive = true; + _firmwareInstall.Sensitive = false; DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); @@ -556,8 +558,193 @@ namespace Ryujinx.Ui _gameLoaded = false; } + + private void Installer_Pressed(object o, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", + this, + FileChooserAction.Open, + "Cancel", + ResponseType.Cancel, + "Open", + ResponseType.Accept); + + if(fileChooser.Run() == (int)ResponseType.Accept) + { + MessageDialog dialog = null; - private void FullScreen_Toggled(object sender, EventArgs args) + try + { + string filename = fileChooser.Filename; + + fileChooser.Dispose(); + + var firmwareVersion = _device.System.VerifyFirmwarePackage(filename); + + if(firmwareVersion== null) + { + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Info, + ButtonsType.Ok, + false, + ""); + + dialog.Text = "Firmware not found."; + + dialog.SecondaryText = $"A valid system firmware was not found in {filename}."; + + Logger.PrintError(LogClass.Application, $"A valid system firmware was not found in {filename}."); + + dialog.Run(); + + dialog.Hide(); + + dialog.Dispose(); + + return; + } + + var currentVersion = _device.System.GetCurrentFirmwareVersion(); + + string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed. "; + + if (currentVersion != null) + { + dialogMessage += $"This will replace the current system version {currentVersion.VersionString}. "; + } + + dialogMessage += "Do you want to continue?"; + + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Question, + ButtonsType.YesNo, + false, + ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + dialog.SecondaryText = dialogMessage; + + int response = dialog.Run(); + + dialog.Dispose(); + + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Info, + ButtonsType.None, + false, + ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + + dialog.SecondaryText = "Installing firmware..."; + + if (response == (int)ResponseType.Yes) + { + Logger.PrintInfo(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new Thread(() => + { + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Run(); + return false; + })); + + try + { + _device.System.InstallFirmware(filename); + + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Dispose(); + + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Info, + ButtonsType.Ok, + false, + ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString}"; + + dialog.SecondaryText = $"System version {firmwareVersion.VersionString} successfully installed."; + + Logger.PrintInfo(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed."); + + dialog.Run(); + dialog.Dispose(); + + return false; + })); + } + catch (Exception ex) + { + + GLib.Idle.Add(new GLib.IdleHandler(() => + { + dialog.Dispose(); + + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Info, + ButtonsType.Ok, + false, + ""); + + dialog.Text = $"Install Firmware {firmwareVersion.VersionString} Failed."; + + dialog.SecondaryText = $"An error occured while installing system version {firmwareVersion.VersionString}." + + "Please check logs for more info."; + + Logger.PrintError(LogClass.Application, ex.Message); + dialog.Run(); + dialog.Dispose(); + return false; + })); + } + }); + + thread.Start(); + } + else + dialog.Dispose(); + } + catch (Exception ex) + { + + if (dialog != null) + { + dialog.Dispose(); + } + + dialog = new MessageDialog(this, + DialogFlags.Modal, + MessageType.Info, + ButtonsType.Ok, + false, + ""); + + dialog.Text = "Parsing Firmware Failed."; + + dialog.SecondaryText = "An error occured while parsing firmware. Please check the logs for more info."; + + Logger.PrintError(LogClass.Application, ex.Message); + + dialog.Run(); + + dialog.Dispose(); + } + } + else + { + fileChooser.Dispose(); + } + } + + private void FullScreen_Toggled(object o, EventArgs args) { if (_fullScreen.Active) { diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index fcf91bc4c5..633154ec1d 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -263,6 +263,21 @@ False Tools True + + + True + False + + + True + False + Install Firmware + True + + + + +