From 1661ce99ca6b533d9f2ad9be4b89e15706cd2af6 Mon Sep 17 00:00:00 2001 From: Xpl0itR Date: Fri, 10 Jan 2020 22:04:52 +0000 Subject: [PATCH 1/6] Update path for system archives in README (#870) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65fc95c5f3..3d415d1be3 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of - **System Titles** - Some of our System Module implementations, like `time`, require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives). You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copying the content to `Ryujinx/nand/system`. + Some of our System Module implementations, like `time`, require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives). You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copying the content to `Ryujinx/bis/system`. - **Executables** From e485ee049d8d9418f3bdceed8d3342cc09977f50 Mon Sep 17 00:00:00 2001 From: emmauss Date: Sun, 12 Jan 2020 02:10:55 +0000 Subject: [PATCH 2/6] System firmware installer (#791) * firmware installer * Add directory installation option and fix 9.x support for directory * Fix missing system font error while installing for the first time * Address code style comments * Create and use InvalidFirmwarePackageException * Fix LDj3SNuD's comments * addressed alex's comments * add label to status bar to show current firmware version Co-authored-by: Thog --- .../InvalidFirmwarePackageException.cs | 9 + .../FileSystem/Content/ContentManager.cs | 573 +++++++++++++++++- .../FileSystem/Content/SystemVersion.cs | 41 ++ Ryujinx.HLE/HOS/Font/SharedFontManager.cs | 20 +- Ryujinx.HLE/HOS/Horizon.cs | 15 + .../HOS/Services/Sdb/Pl/ISharedFontManager.cs | 2 +- Ryujinx/Ui/MainWindow.cs | 202 +++++- Ryujinx/Ui/MainWindow.glade | 101 ++- 8 files changed, 936 insertions(+), 27 deletions(-) create mode 100644 Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs create mode 100644 Ryujinx.HLE/FileSystem/Content/SystemVersion.cs diff --git a/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs b/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs new file mode 100644 index 0000000000..bddd827a01 --- /dev/null +++ b/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + class InvalidFirmwarePackageException : Exception + { + public InvalidFirmwarePackageException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index 17faa19f62..b83ae44064 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -1,22 +1,30 @@ -using LibHac.FsSystem; +using LibHac; +using LibHac.Fs; +using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; +using Ryujinx.HLE.Exceptions; 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; namespace Ryujinx.HLE.FileSystem.Content { internal class ContentManager { + private const ulong SystemVersionTitleId = 0x0100000000000809; + private const ulong SystemUpdateTitleId = 0x0100000000000816; + private Dictionary> _locationEntries; - private Dictionary _sharedFontTitleDictionary; + private Dictionary _sharedFontTitleDictionary; private Dictionary _sharedFontFilenameDictionary; - private SortedDictionary<(ulong, NcaContentType), string> _contentDictionary; + private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; private Switch _device; @@ -48,9 +56,10 @@ namespace Ryujinx.HLE.FileSystem.Content _device = device; } - public void LoadEntries() + public void LoadEntries(bool ignoreMissingFonts = false) { _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, ignoreMissingFonts); } 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,9 +184,10 @@ namespace Ryujinx.HLE.FileSystem.Content { if (_contentDictionary.ContainsValue(ncaId)) { - var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); - long titleId = (long)content.Key.Item1; - NcaContentType contentType = content.Key.Item2; + var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); + long titleId = (long)content.Key.Item1; + + NcaContentType contentType = content.Key.type; 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,539 @@ 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 temporaryDirectory = Path.Combine(contentDirectory, "temp"); + + if (Directory.Exists(temporaryDirectory)) + { + Directory.Delete(temporaryDirectory, true); + } + + if (Directory.Exists(firmwareSource)) + { + InstallFromDirectory(firmwareSource, temporaryDirectory); + FinishInstallation(temporaryDirectory, 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, temporaryDirectory); + } + break; + case ".xci": + Xci xci = new Xci(_device.System.KeySet, file.AsStorage()); + InstallFromCart(xci, temporaryDirectory); + break; + default: + throw new InvalidFirmwarePackageException("Input file is not a valid firmware package"); + } + + FinishInstallation(temporaryDirectory, registeredDirectory); + } + } + + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) + { + if (Directory.Exists(registeredDirectory)) + { + new DirectoryInfo(registeredDirectory).Delete(true); + } + + Directory.Move(temporaryDirectory, registeredDirectory); + + LoadEntries(); + } + + private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory) + { + InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory); + } + + private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory) + { + 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('.')), temporaryDirectory); + } + } + + private void InstallFromCart(Xci gameCard, string temporaryDirectory) + { + if (gameCard.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); + + InstallFromPartition(partition, temporaryDirectory); + } + else + { + throw new Exception("Update not found in xci file."); + } + } + + private void InstallFromZip(ZipArchive archive, string temporaryDirectory) + { + 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(temporaryDirectory, ncaId); + + Directory.CreateDirectory(newPath); + + entry.ExtractToFile(Path.Combine(newPath, "00")); + } + } + } + } + } + + public void SaveNca(Nca nca, string ncaId, string temporaryDirectory) + { + string newPath = Path.Combine(temporaryDirectory, 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) + { + 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 InvalidFirmwarePackageException("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.type == NcaContentType.Meta).path; + + 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).IsSuccess()) + { + 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.type != NcaContentType.Meta).path; + + 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).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.AsStream()); + } + } + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) + { + metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // 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).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + IStorage contentStorage = contentNcaStream.AsStorage(); + if (contentStorage.GetSize(out long size).IsSuccess()) + { + 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.path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{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).IsSuccess()) + { + 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).IsSuccess()) + { + 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.type == NcaContentType.Meta); + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // 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; + } + + IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage(); + IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, 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).IsSuccess()) + { + var meta = new Cnmt(metaFile.AsStream()); + + if (contentStorage.GetSize(out long size).IsSuccess()) + { + 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.path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + + return systemVersion; + } + + return null; + } + + public SystemVersion GetCurrentFirmwareVersion() + { + LoadEntries(true); + + 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 == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + if (romfs.OpenFile(out IFile systemVersionFile, "/file", OpenMode.Read).IsSuccess()) + { + 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..08ec351255 --- /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; } + public byte Minor { get; } + public byte Micro { get; } + public byte RevisionMajor { get; } + public byte RevisionMinor { get; } + public string PlatformString { get; } + public string Hex { get; } + public string VersionString { get; } + public string VersionTitle { get; } + + 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..e126cd5799 100644 --- a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs @@ -44,7 +44,15 @@ namespace Ryujinx.HLE.HOS.Font _fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts"); } - public void EnsureInitialized(ContentManager contentManager) + public void Initialize(ContentManager contentManager, bool ignoreMissingFonts) + { + _fontData?.Clear(); + _fontData = null; + + EnsureInitialized(contentManager, ignoreMissingFonts); + } + + public void EnsureInitialized(ContentManager contentManager, bool ignoreMissingFonts) { if (_fontData == null) { @@ -112,10 +120,12 @@ namespace Ryujinx.HLE.HOS.Font return info; } - else + else if (!ignoreMissingFonts) { throw new InvalidSystemResourceException($"Font \"{name}.ttf\" not found. Please provide it in \"{_fontsPath}\"."); } + + return new FontInfo(); } _fontData = new Dictionary @@ -128,7 +138,7 @@ namespace Ryujinx.HLE.HOS.Font { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") } }; - if (fontOffset > Horizon.FontSize) + if (fontOffset > Horizon.FontSize && !ignoreMissingFonts) { throw new InvalidSystemResourceException( $"The sum of all fonts size exceed the shared memory size. " + @@ -151,14 +161,14 @@ namespace Ryujinx.HLE.HOS.Font public int GetFontSize(SharedFontType fontType) { - EnsureInitialized(_device.System.ContentManager); + EnsureInitialized(_device.System.ContentManager, false); return _fontData[fontType].Size; } public int GetSharedMemoryAddressOffset(SharedFontType fontType) { - EnsureInitialized(_device.System.ContentManager); + EnsureInitialized(_device.System.ContentManager, false); return _fontData[fontType].Offset + 8; } 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.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs index 4560d9545c..418c15f2c2 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs @@ -61,7 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl // GetSharedMemoryNativeHandle() -> handle public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) { - context.Device.System.Font.EnsureInitialized(context.Device.System.ContentManager); + context.Device.System.Font.EnsureInitialized(context.Device.System.ContentManager, false); if (context.Process.HandleTable.GenerateHandle(context.Device.System.FontSharedMem, out int handle) != KernelResult.Success) { diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 2c4d011268..667ea5a5b9 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -44,6 +44,8 @@ namespace Ryujinx.Ui [GUI] CheckMenuItem _fullScreen; [GUI] MenuItem _stopEmulation; [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstallFile; + [GUI] MenuItem _firmwareInstallDirectory; [GUI] CheckMenuItem _iconToggle; [GUI] CheckMenuItem _appToggle; [GUI] CheckMenuItem _developerToggle; @@ -56,6 +58,7 @@ namespace Ryujinx.Ui [GUI] TreeView _gameTable; [GUI] TreeSelection _gameTableSelection; [GUI] Label _progressLabel; + [GUI] Label _firmwareVersionLabel; [GUI] LevelBar _progressBar; #pragma warning restore CS0649 #pragma warning restore IDE0044 @@ -134,6 +137,8 @@ namespace Ryujinx.Ui #pragma warning disable CS4014 UpdateGameTable(); #pragma warning restore CS4014 + + Task.Run(RefreshFirmwareLabel); } internal static void ApplyTheme() @@ -297,6 +302,9 @@ namespace Ryujinx.Ui _gameLoaded = true; _stopEmulation.Sensitive = true; + _firmwareInstallFile.Sensitive = false; + _firmwareInstallDirectory.Sensitive = false; + DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui"); @@ -556,7 +564,199 @@ namespace Ryujinx.Ui _gameLoaded = false; } - private void FullScreen_Toggled(object sender, EventArgs args) + private void Installer_File_Pressed(object o, EventArgs args) + { + FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open", + this, + FileChooserAction.Open, + "Cancel", + ResponseType.Cancel, + "Open", + ResponseType.Accept); + + fileChooser.Filter = new FileFilter(); + fileChooser.Filter.AddPattern("*.zip"); + fileChooser.Filter.AddPattern("*.xci"); + + HandleInstallerDialog(fileChooser); + } + + private void Installer_Directory_Pressed(object o, EventArgs args) + { + FileChooserDialog directoryChooser = new FileChooserDialog("Choose the firmware directory to open", + this, + FileChooserAction.SelectFolder, + "Cancel", + ResponseType.Cancel, + "Open", + ResponseType.Accept); + + HandleInstallerDialog(directoryChooser); + } + + private void RefreshFirmwareLabel() + { + var currentFirmware = _device.System.GetCurrentFirmwareVersion(); + + GLib.Idle.Add(new GLib.IdleHandler(() => + { + _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; + + return false; + })); + } + + private void HandleInstallerDialog(FileChooserDialog fileChooser) + { + if (fileChooser.Run() == (int)ResponseType.Accept) + { + MessageDialog dialog = null; + + 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; + })); + } + finally + { + RefreshFirmwareLabel(); + } + }); + + 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..8e2eab9391 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -1,5 +1,5 @@ - + @@ -8,9 +8,6 @@ center 1280 750 - - - True @@ -263,6 +260,44 @@ False Tools True + + + True + False + + + True + False + Install Firmware + True + + + True + False + + + True + False + Install a firmware from XCI or ZIP + True + + + + + + True + False + Install a firmware from a directory + True + + + + + + + + + @@ -370,7 +405,7 @@ True False - 5 + 10 5 2 2 @@ -388,7 +423,7 @@ True False start - 5 + 10 5 @@ -397,6 +432,57 @@ 2 + + + True + False + + + False + True + 3 + + + + + True + False + 5 + + + True + False + System Version + + + False + True + 0 + + + + + 50 + True + False + 5 + 5 + + + False + True + end + 1 + + + + + False + True + end + 4 + + False @@ -413,5 +499,8 @@ + + + From 29e8576b0d056a9c3a32b93bf47cafbb073e9f9c Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 12 Jan 2020 03:14:27 +0100 Subject: [PATCH 3/6] MapBufferEx: take page size into account (#873) Fix #744 --- Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs | 5 +++-- .../NvHostAsGpu/NvHostAsGpuDeviceFile.cs | 21 +++++++++++++------ .../NvHostAsGpu/Types/AddressSpaceContext.cs | 6 +++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 2fc315c3cd..ffca6f339b 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -66,12 +66,13 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// CPU virtual address to map into /// Size in bytes of the mapping + /// Required alignment of the GPU virtual address in bytes /// GPU virtual address where the range was mapped, or an all ones mask in case of failure - public ulong Map(ulong pa, ulong size) + public ulong MapAllocate(ulong pa, ulong size, ulong alignment) { lock (_pageTable) { - ulong va = GetFreePosition(size); + ulong va = GetFreePosition(size, alignment); if (va != PteUnmapped) { diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs index 008c6059ba..6653c0f0eb 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -1,10 +1,12 @@ -using Ryujinx.Common.Logging; +using Ryujinx.Common; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; using System; using System.Collections.Concurrent; +using System.Diagnostics; namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu { @@ -165,7 +167,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments) { - const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!"; + const string mapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!"; AddressSpaceContext addressSpaceContext = GetAddressSpaceContext(Context); @@ -178,6 +180,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu return NvInternalResult.InvalidInput; } + ulong pageSize = (ulong)arguments.PageSize; + + if (pageSize == 0) + { + pageSize = (ulong)map.Align; + } + long physicalAddress; if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0) @@ -192,7 +201,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu if ((long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)virtualAddress, (ulong)arguments.MappingSize) < 0) { - string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize); + string message = string.Format(mapErrorMsg, virtualAddress, arguments.MappingSize, pageSize); Logger.PrintWarning(LogClass.ServiceNv, message); @@ -229,13 +238,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu if (!virtualAddressAllocated) { - if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size)) + if (addressSpaceContext.ValidateFixedBuffer(arguments.Offset, size, pageSize)) { arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)arguments.Offset, (ulong)size); } else { - string message = string.Format(mapErrorMsg, arguments.Offset, size); + string message = string.Format(mapErrorMsg, arguments.Offset, size, pageSize); Logger.PrintWarning(LogClass.ServiceNv, message); @@ -244,7 +253,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu } else { - arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, (ulong)size); + arguments.Offset = (long)addressSpaceContext.Gmm.MapAllocate((ulong)physicalAddress, (ulong)size, pageSize); } if (arguments.Offset < 0) diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs index 951994efcb..d39be2977c 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types _reservations = new SortedList(); } - public bool ValidateFixedBuffer(long position, long size) + public bool ValidateFixedBuffer(long position, long size, ulong alignment) { long mapEnd = position + size; @@ -58,8 +58,8 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types return false; } - // Check if address is page aligned. - if ((position & (long)MemoryManager.PageMask) != 0) + // Check if address is aligned. + if ((position & (long)(alignment - 1)) != 0) { return false; } From 78f6b1d3968a6c439cb36af74cae13c040c61af1 Mon Sep 17 00:00:00 2001 From: Jonathan Goyvaerts Date: Sun, 12 Jan 2020 04:01:04 +0100 Subject: [PATCH 4/6] Create method to LoadAndSave ApplicationMetaData and get rid of code duplication (#872) --- Ryujinx/Ui/ApplicationLibrary.cs | 39 +++++++----- Ryujinx/Ui/ApplicationMetadata.cs | 2 +- Ryujinx/Ui/MainWindow.cs | 99 +++++-------------------------- 3 files changed, 39 insertions(+), 101 deletions(-) diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs index 90d333a252..2ab6107776 100644 --- a/Ryujinx/Ui/ApplicationLibrary.cs +++ b/Ryujinx/Ui/ApplicationLibrary.cs @@ -34,9 +34,8 @@ namespace Ryujinx.Ui private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png"); private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png"); - private static Keyset _keySet; - private static TitleLanguage _desiredTitleLanguage; - private static ApplicationMetadata _appMetadata; + private static Keyset _keySet; + private static TitleLanguage _desiredTitleLanguage; public static void LoadApplications(List appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage, FileSystemClient fsClient = null, VirtualFileSystem vfs = null) { @@ -339,7 +338,7 @@ namespace Ryujinx.Ui } } - (bool favorite, string timePlayed, string lastPlayed) = GetMetadata(titleId); + ApplicationMetadata appMetadata = LoadAndSaveMetaData(titleId); if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNum)) { @@ -357,14 +356,14 @@ namespace Ryujinx.Ui ApplicationData data = new ApplicationData() { - Favorite = favorite, + Favorite = appMetadata.Favorite, Icon = applicationIcon, TitleName = titleName, TitleId = titleId, Developer = developer, Version = version, - TimePlayed = timePlayed, - LastPlayed = lastPlayed, + TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed), + LastPlayed = appMetadata.LastPlayed, FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1), FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB", Path = applicationPath, @@ -431,34 +430,44 @@ namespace Ryujinx.Ui return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); } - private static (bool favorite, string timePlayed, string lastPlayed) GetMetadata(string titleId) + internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) { string metadataFolder = Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui"); - string metadataFile = Path.Combine(metadataFolder, "metadata.json"); + string metadataFile = Path.Combine(metadataFolder, "metadata.json"); - IJsonFormatterResolver resolver = CompositeResolver.Create(StandardResolver.AllowPrivateSnakeCase); + IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); + + ApplicationMetadata appMetadata; if (!File.Exists(metadataFile)) { Directory.CreateDirectory(metadataFolder); - _appMetadata = new ApplicationMetadata + appMetadata = new ApplicationMetadata { Favorite = false, TimePlayed = 0, LastPlayed = "Never" }; - byte[] saveData = JsonSerializer.Serialize(_appMetadata, resolver); - File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + byte[] data = JsonSerializer.Serialize(appMetadata, resolver); + File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); } using (Stream stream = File.OpenRead(metadataFile)) { - _appMetadata = JsonSerializer.Deserialize(stream, resolver); + appMetadata = JsonSerializer.Deserialize(stream, resolver); } - return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed); + if (modifyFunction != null) + { + modifyFunction(appMetadata); + + byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver); + File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + } + + return appMetadata; } private static string ConvertSecondsToReadableString(double seconds) diff --git a/Ryujinx/Ui/ApplicationMetadata.cs b/Ryujinx/Ui/ApplicationMetadata.cs index adc2b9df97..cdedf91b80 100644 --- a/Ryujinx/Ui/ApplicationMetadata.cs +++ b/Ryujinx/Ui/ApplicationMetadata.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Ui { - internal struct ApplicationMetadata + internal class ApplicationMetadata { public bool Favorite { get; set; } public double TimePlayed { get; set; } diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 667ea5a5b9..c914a4a7a3 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -307,37 +307,10 @@ namespace Ryujinx.Ui DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); - string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui"); - string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json"); - - IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); - - ApplicationMetadata appMetadata; - - if (!File.Exists(metadataFile)) + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleId, appMetadata => { - Directory.CreateDirectory(metadataFolder); - - appMetadata = new ApplicationMetadata - { - Favorite = false, - TimePlayed = 0, - LastPlayed = "Never" - }; - - byte[] data = JsonSerializer.Serialize(appMetadata, resolver); - File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); - } - - using (Stream stream = File.OpenRead(metadataFile)) - { - appMetadata = JsonSerializer.Deserialize(stream, resolver); - } - - appMetadata.LastPlayed = DateTime.UtcNow.ToString(); - - byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver); - File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + appMetadata.LastPlayed = DateTime.UtcNow.ToString(); + }); } } @@ -364,40 +337,13 @@ namespace Ryujinx.Ui if (_gameLoaded) { - string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui"); - string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json"); - - IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); - - ApplicationMetadata appMetadata; - - if (!File.Exists(metadataFile)) + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleId, appMetadata => { - Directory.CreateDirectory(metadataFolder); + DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); + double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; - appMetadata = new ApplicationMetadata - { - Favorite = false, - TimePlayed = 0, - LastPlayed = "Never" - }; - - byte[] data = JsonSerializer.Serialize(appMetadata, resolver); - File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson()); - } - - using (Stream stream = File.OpenRead(metadataFile)) - { - appMetadata = JsonSerializer.Deserialize(stream, resolver); - } - - DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); - double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; - - appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); - - byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver); - File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); + }); } Profile.FinishProfiling(); @@ -453,33 +399,16 @@ namespace Ryujinx.Ui { _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); - string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); - string metadataPath = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui", "metadata.json"); + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); - IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase }); + bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); - ApplicationMetadata appMetadata; + _tableStore.SetValue(treeIter, 0, newToggleValue); - using (Stream stream = File.OpenRead(metadataPath)) + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => { - appMetadata = JsonSerializer.Deserialize(stream, resolver); - } - - if ((bool)_tableStore.GetValue(treeIter, 0)) - { - _tableStore.SetValue(treeIter, 0, false); - - appMetadata.Favorite = false; - } - else - { - _tableStore.SetValue(treeIter, 0, true); - - appMetadata.Favorite = true; - } - - byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver); - File.WriteAllText(metadataPath, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson()); + appMetadata.Favorite = newToggleValue; + }); } private void Row_Activated(object sender, RowActivatedArgs args) From 9c8d48edff8e43318550a087816542391dffea83 Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 12 Jan 2020 12:06:26 +0100 Subject: [PATCH 5/6] Add 32 bits support to HleProcessDebugger (#859) Co-authored-by: riperiperi --- .../HOS/Kernel/Process/HleProcessDebugger.cs | 121 +++++++++++++----- Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs | 8 +- Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs | 12 ++ Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs | 12 ++ 4 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs create mode 100644 Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs diff --git a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs index 4219eb7585..75f52851de 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -1,9 +1,12 @@ using ARMeilleure.Memory; +using Ryujinx.Common; using Ryujinx.HLE.HOS.Diagnostics.Demangler; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.Loaders.Elf; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -72,25 +75,43 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } - // TODO: ARM32. - long framePointer = (long)context.GetX(29); - trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}"); - while (framePointer != 0) + if (context.IsAarch32) { - if ((framePointer & 7) != 0 || - !_owner.CpuMemory.IsMapped(framePointer) || - !_owner.CpuMemory.IsMapped(framePointer + 8)) + long framePointer = (long)context.GetX(11); + + while (framePointer != 0) { - break; + if ((framePointer & 3) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 4)) + { + break; + } + + AppendTrace(_owner.CpuMemory.ReadInt32(framePointer + 4)); + + framePointer = _owner.CpuMemory.ReadInt32(framePointer); } + } + else + { + long framePointer = (long)context.GetX(29); - // Note: This is the return address, we need to subtract one instruction - // worth of bytes to get the branch instruction address. - AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8) - 4); + while (framePointer != 0) + { + if ((framePointer & 7) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 8)) + { + break; + } - framePointer = _owner.CpuMemory.ReadInt64(framePointer); + AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8)); + + framePointer = _owner.CpuMemory.ReadInt64(framePointer); + } } return trace.ToString(); @@ -111,9 +132,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process ElfSymbol symbol = image.Symbols[middle]; - long endAddr = symbol.Value + symbol.Size; + ulong endAddr = symbol.Value + symbol.Size; - if ((ulong)address >= (ulong)symbol.Value && (ulong)address < (ulong)endAddr) + if ((ulong)address >= symbol.Value && (ulong)address < endAddr) { name = symbol.Name; @@ -242,13 +263,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Process long ehHdrEndOffset = memory.ReadInt32(mod0Offset + 0x14) + mod0Offset; long modObjOffset = memory.ReadInt32(mod0Offset + 0x18) + mod0Offset; - // TODO: Elf32. + bool isAArch32 = memory.ReadUInt64(dynamicOffset) > 0xFFFFFFFF || memory.ReadUInt64(dynamicOffset + 0x10) > 0xFFFFFFFF; + while (true) { - long tagVal = memory.ReadInt64(dynamicOffset + 0); - long value = memory.ReadInt64(dynamicOffset + 8); + long tagVal; + long value; + + if (isAArch32) + { + tagVal = memory.ReadInt32(dynamicOffset + 0); + value = memory.ReadInt32(dynamicOffset + 4); + + dynamicOffset += 0x8; + } + else + { + tagVal = memory.ReadInt64(dynamicOffset + 0); + value = memory.ReadInt64(dynamicOffset + 8); + + dynamicOffset += 0x10; + } - dynamicOffset += 0x10; ElfDynamicTag tag = (ElfDynamicTag)tagVal; @@ -274,7 +310,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process while ((ulong)symTblAddr < (ulong)strTblAddr) { - ElfSymbol sym = GetSymbol(memory, symTblAddr, strTblAddr); + ElfSymbol sym = isAArch32 ? GetSymbol32(memory, symTblAddr, strTblAddr) : GetSymbol64(memory, symTblAddr, strTblAddr); symbols.Add(sym); @@ -287,23 +323,42 @@ namespace Ryujinx.HLE.HOS.Kernel.Process } } - private ElfSymbol GetSymbol(MemoryManager memory, long address, long strTblAddr) + private ElfSymbol GetSymbol64(MemoryManager memory, long address, long strTblAddr) { - int nameIndex = memory.ReadInt32(address + 0); - int info = memory.ReadByte (address + 4); - int other = memory.ReadByte (address + 5); - int shIdx = memory.ReadInt16(address + 6); - long value = memory.ReadInt64(address + 8); - long size = memory.ReadInt64(address + 16); - - string name = string.Empty; - - for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(memory.ReadBytes(address, Unsafe.SizeOf())))) { - name += (char)chr; - } + ElfSymbol64 sym = inputStream.ReadStruct(); - return new ElfSymbol(name, info, other, shIdx, value, size); + uint nameIndex = sym.NameOffset; + + string name = string.Empty; + + for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + { + name += (char)chr; + } + + return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } + } + + private ElfSymbol GetSymbol32(MemoryManager memory, long address, long strTblAddr) + { + using (BinaryReader inputStream = new BinaryReader(new MemoryStream(memory.ReadBytes(address, Unsafe.SizeOf())))) + { + ElfSymbol32 sym = inputStream.ReadStruct(); + + uint nameIndex = sym.NameOffset; + + string name = string.Empty; + + for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + { + name += (char)chr; + } + + return new ElfSymbol(name, sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs index b655816f14..9961afe195 100644 --- a/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs @@ -17,16 +17,16 @@ namespace Ryujinx.HLE.Loaders.Elf Binding == ElfSymbolBinding.StbWeak; public int ShIdx { get; private set; } - public long Value { get; private set; } - public long Size { get; private set; } + public ulong Value { get; private set; } + public ulong Size { get; private set; } public ElfSymbol( string name, int info, int other, int shIdx, - long value, - long size) + ulong value, + ulong size) { Name = name; Type = (ElfSymbolType)(info & 0xf); diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs new file mode 100644 index 0000000000..b7d9850636 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol32 + { + public uint NameOffset; + public uint ValueAddress; + public uint Size; + public char Info; + public char Other; + public ushort SectionIndex; + } +} diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs new file mode 100644 index 0000000000..662de1ff1b --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol64 + { + public uint NameOffset; + public char Info; + public char Other; + public ushort SectionIndex; + public ulong ValueAddress; + public ulong Size; + } +} From e348f95495d3af9742010b1e44a2c3d7f30dba1a Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 12 Jan 2020 04:15:17 -0700 Subject: [PATCH 6/6] Call EnsureApplicationSaveData when launching a game (#871) * Workaround for the lack of a program registry * Call EnsureApplicationSaveData when launching a game --- Ryujinx.HLE/HOS/Horizon.cs | 51 +++++++++++++++++-- .../Services/Arp/ApplicationLaunchProperty.cs | 6 +-- .../HOS/Services/Fs/IFileSystemProxy.cs | 30 +++++++++++ Ryujinx/Ui/GLScreen.cs | 6 +-- Ryujinx/Ui/MainWindow.cs | 6 +-- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 855d89148c..428e19227d 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -1,9 +1,11 @@ using LibHac; +using LibHac.Account; using LibHac.Common; using LibHac.Fs; using LibHac.FsService; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; +using LibHac.Ncm; using LibHac.Ns; using LibHac.Spl; using Ryujinx.Common.Logging; @@ -32,6 +34,8 @@ using System.Threading; using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager; using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject; +using static LibHac.Fs.ApplicationSaveDataManagement; + namespace Ryujinx.HLE.HOS { public class Horizon : IDisposable @@ -109,7 +113,8 @@ namespace Ryujinx.HLE.HOS public string TitleName { get; private set; } - public string TitleId { get; private set; } + public ulong TitleId { get; private set; } + public string TitleIdText => TitleId.ToString("x16"); public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } @@ -513,7 +518,7 @@ namespace Ryujinx.HLE.HOS LoadExeFs(codeFs, out Npdm metaData); - TitleId = metaData.Aci0.TitleId.ToString("x16"); + TitleId = metaData.Aci0.TitleId; if (controlNca != null) { @@ -523,6 +528,11 @@ namespace Ryujinx.HLE.HOS { ControlData.ByteSpan.Clear(); } + + if (TitleId != 0) + { + EnsureSaveData(new TitleId(TitleId)); + } } private void LoadExeFs(IFileSystem codeFs, out Npdm metaData) @@ -561,7 +571,7 @@ namespace Ryujinx.HLE.HOS } } - TitleId = metaData.Aci0.TitleId.ToString("x16"); + TitleId = metaData.Aci0.TitleId; LoadNso("rtld"); LoadNso("main"); @@ -664,7 +674,7 @@ namespace Ryujinx.HLE.HOS ContentManager.LoadEntries(); TitleName = metaData.TitleName; - TitleId = metaData.Aci0.TitleId.ToString("x16"); + TitleId = metaData.Aci0.TitleId; ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject }); } @@ -679,6 +689,39 @@ namespace Ryujinx.HLE.HOS } } + private Result EnsureSaveData(TitleId titleId) + { + Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists."); + + UInt128 lastOpenedUser = State.Account.LastOpenedUser.UserId; + Uid user = new Uid((ulong)lastOpenedUser.Low, (ulong)lastOpenedUser.High); + + ref ApplicationControlProperty control = ref ControlData.Value; + + if (LibHac.Util.IsEmpty(ControlData.ByteSpan)) + { + // 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. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.PrintWarning(LogClass.Application, + "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Result rc = EnsureApplicationSaveData(FsClient, out _, titleId, ref ControlData.Value, ref user); + + if (rc.IsFailure()) + { + Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}"); + } + + return rc; + } + public void LoadKeySet() { string keyFile = null; diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs index 4962e3ffdc..686577d7e3 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -1,12 +1,10 @@ using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.Utilities; -using System; namespace Ryujinx.HLE.HOS.Services.Arp { class ApplicationLaunchProperty { - public long TitleId; + public ulong TitleId; public int Version; public byte BaseGameStorageId; public byte UpdateGameStorageId; @@ -33,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp return new ApplicationLaunchProperty { - TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleId), 0), + TitleId = context.Device.System.TitleId, Version = 0x00, BaseGameStorageId = (byte)StorageId.NandSystem, UpdateGameStorageId = (byte)StorageId.None diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 60f4a3f434..4e96788644 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -133,6 +133,20 @@ namespace Ryujinx.HLE.HOS.Services.Fs SaveDataCreateInfo createInfo = context.RequestData.ReadStruct(); SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID and owner ID if they're not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + if (createInfo.OwnerId == TitleId.Zero) + { + createInfo.OwnerId = new TitleId(context.Process.TitleId); + } + + Logger.PrintInfo(LogClass.ServiceFs, $"Creating save with title ID {attribute.TitleId.Value:x16}"); + Result result = _baseFileSystemProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaCreateInfo); return (ResultCode)result.Value; @@ -196,6 +210,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct(); HashSalt hashSalt = context.RequestData.ReadStruct(); + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID and owner ID if they're not already set + if (attribute.TitleId == TitleId.Zero) + { + attribute.TitleId = new TitleId(context.Process.TitleId); + } + + if (createInfo.OwnerId == TitleId.Zero) + { + createInfo.OwnerId = new TitleId(context.Process.TitleId); + } + Result result = _baseFileSystemProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSalt); return (ResultCode)result.Value; @@ -208,6 +234,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID if it's not already set if (attribute.TitleId == TitleId.Zero) { attribute.TitleId = new TitleId(context.Process.TitleId); @@ -247,6 +275,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); SaveDataAttribute attribute = context.RequestData.ReadStruct(); + // TODO: There's currently no program registry for FS to reference. + // Workaround that by setting the application ID if it's not already set if (attribute.TitleId == TitleId.Zero) { attribute.TitleId = new TitleId(context.Process.TitleId); diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 6c0c28586b..8e39126224 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -307,10 +307,10 @@ namespace Ryujinx.Ui string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty : " | " + _device.System.TitleName; - string titleIDSection = string.IsNullOrWhiteSpace(_device.System.TitleId) ? string.Empty - : " | " + _device.System.TitleId.ToUpper(); + string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty + : " | " + _device.System.TitleIdText.ToUpper(); - _newTitle = $"Ryujinx{titleNameSection}{titleIDSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + + _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " + $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}"; _titleEvent = true; diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index c914a4a7a3..af7dd524ab 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -305,9 +305,9 @@ namespace Ryujinx.Ui _firmwareInstallFile.Sensitive = false; _firmwareInstallDirectory.Sensitive = false; - DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleId, _device.System.TitleName); + DiscordIntegrationModule.SwitchToPlayingState(_device.System.TitleIdText, _device.System.TitleName); - ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleId, appMetadata => + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => { appMetadata.LastPlayed = DateTime.UtcNow.ToString(); }); @@ -337,7 +337,7 @@ namespace Ryujinx.Ui if (_gameLoaded) { - ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleId, appMetadata => + ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata => { DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;