Call EnsureApplicationSaveData when launching a game (#871)
* Workaround for the lack of a program registry * Call EnsureApplicationSaveData when launching a game
This commit is contained in:
parent
9c8d48edff
commit
e348f95495
5 changed files with 85 additions and 14 deletions
|
@ -1,9 +1,11 @@
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using LibHac.Account;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsService;
|
using LibHac.FsService;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
using LibHac.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Spl;
|
using LibHac.Spl;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
@ -32,6 +34,8 @@ using System.Threading;
|
||||||
using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager;
|
using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager;
|
||||||
using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
|
using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
|
||||||
|
|
||||||
|
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS
|
namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
public class Horizon : IDisposable
|
public class Horizon : IDisposable
|
||||||
|
@ -109,7 +113,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
public string TitleName { get; private set; }
|
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; }
|
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
|
||||||
|
|
||||||
|
@ -513,7 +518,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
LoadExeFs(codeFs, out Npdm metaData);
|
LoadExeFs(codeFs, out Npdm metaData);
|
||||||
|
|
||||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
TitleId = metaData.Aci0.TitleId;
|
||||||
|
|
||||||
if (controlNca != null)
|
if (controlNca != null)
|
||||||
{
|
{
|
||||||
|
@ -523,6 +528,11 @@ namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
ControlData.ByteSpan.Clear();
|
ControlData.ByteSpan.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TitleId != 0)
|
||||||
|
{
|
||||||
|
EnsureSaveData(new TitleId(TitleId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
|
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("rtld");
|
||||||
LoadNso("main");
|
LoadNso("main");
|
||||||
|
@ -664,7 +674,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
ContentManager.LoadEntries();
|
ContentManager.LoadEntries();
|
||||||
|
|
||||||
TitleName = metaData.TitleName;
|
TitleName = metaData.TitleName;
|
||||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
TitleId = metaData.Aci0.TitleId;
|
||||||
|
|
||||||
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
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<ApplicationControlProperty>(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()
|
public void LoadKeySet()
|
||||||
{
|
{
|
||||||
string keyFile = null;
|
string keyFile = null;
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.Utilities;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Arp
|
namespace Ryujinx.HLE.HOS.Services.Arp
|
||||||
{
|
{
|
||||||
class ApplicationLaunchProperty
|
class ApplicationLaunchProperty
|
||||||
{
|
{
|
||||||
public long TitleId;
|
public ulong TitleId;
|
||||||
public int Version;
|
public int Version;
|
||||||
public byte BaseGameStorageId;
|
public byte BaseGameStorageId;
|
||||||
public byte UpdateGameStorageId;
|
public byte UpdateGameStorageId;
|
||||||
|
@ -33,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||||
|
|
||||||
return new ApplicationLaunchProperty
|
return new ApplicationLaunchProperty
|
||||||
{
|
{
|
||||||
TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleId), 0),
|
TitleId = context.Device.System.TitleId,
|
||||||
Version = 0x00,
|
Version = 0x00,
|
||||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||||
UpdateGameStorageId = (byte)StorageId.None
|
UpdateGameStorageId = (byte)StorageId.None
|
||||||
|
|
|
@ -133,6 +133,20 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
SaveDataCreateInfo createInfo = context.RequestData.ReadStruct<SaveDataCreateInfo>();
|
SaveDataCreateInfo createInfo = context.RequestData.ReadStruct<SaveDataCreateInfo>();
|
||||||
SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct<SaveMetaCreateInfo>();
|
SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct<SaveMetaCreateInfo>();
|
||||||
|
|
||||||
|
// 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);
|
Result result = _baseFileSystemProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaCreateInfo);
|
||||||
|
|
||||||
return (ResultCode)result.Value;
|
return (ResultCode)result.Value;
|
||||||
|
@ -196,6 +210,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct<SaveMetaCreateInfo>();
|
SaveMetaCreateInfo metaCreateInfo = context.RequestData.ReadStruct<SaveMetaCreateInfo>();
|
||||||
HashSalt hashSalt = context.RequestData.ReadStruct<HashSalt>();
|
HashSalt hashSalt = context.RequestData.ReadStruct<HashSalt>();
|
||||||
|
|
||||||
|
// 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);
|
Result result = _baseFileSystemProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaCreateInfo, ref hashSalt);
|
||||||
|
|
||||||
return (ResultCode)result.Value;
|
return (ResultCode)result.Value;
|
||||||
|
@ -208,6 +234,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64();
|
SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64();
|
||||||
SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>();
|
SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>();
|
||||||
|
|
||||||
|
// 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)
|
if (attribute.TitleId == TitleId.Zero)
|
||||||
{
|
{
|
||||||
attribute.TitleId = new TitleId(context.Process.TitleId);
|
attribute.TitleId = new TitleId(context.Process.TitleId);
|
||||||
|
@ -247,6 +275,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||||
SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64();
|
SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64();
|
||||||
SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>();
|
SaveDataAttribute attribute = context.RequestData.ReadStruct<SaveDataAttribute>();
|
||||||
|
|
||||||
|
// 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)
|
if (attribute.TitleId == TitleId.Zero)
|
||||||
{
|
{
|
||||||
attribute.TitleId = new TitleId(context.Process.TitleId);
|
attribute.TitleId = new TitleId(context.Process.TitleId);
|
||||||
|
|
|
@ -307,10 +307,10 @@ namespace Ryujinx.Ui
|
||||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
||||||
: " | " + _device.System.TitleName;
|
: " | " + _device.System.TitleName;
|
||||||
|
|
||||||
string titleIDSection = string.IsNullOrWhiteSpace(_device.System.TitleId) ? string.Empty
|
string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
|
||||||
: " | " + _device.System.TitleId.ToUpper();
|
: " | " + _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")}";
|
$"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
|
||||||
|
|
||||||
_titleEvent = true;
|
_titleEvent = true;
|
||||||
|
|
|
@ -305,9 +305,9 @@ namespace Ryujinx.Ui
|
||||||
_firmwareInstallFile.Sensitive = false;
|
_firmwareInstallFile.Sensitive = false;
|
||||||
_firmwareInstallDirectory.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();
|
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||||
});
|
});
|
||||||
|
@ -337,7 +337,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
if (_gameLoaded)
|
if (_gameLoaded)
|
||||||
{
|
{
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(_device.System.TitleIdText, appMetadata =>
|
||||||
{
|
{
|
||||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||||
|
|
Loading…
Reference in a new issue