diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 855d89148..428e19227 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 4962e3ffd..686577d7e 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 60f4a3f43..4e9678864 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 6c0c28586..8e3912622 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 c914a4a7a..af7dd524a 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;