mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-11 23:40:35 +00:00
[HLE] Fix StoreData layout and implement IDatabaseService.Append (#43)
- Fixes `StoreData` layout/update handling so `UpdateLatest` returns the stored Mii data correctly. - Implements `IDatabaseService.Append` (https://switchbrew.org/wiki/Shared_Database_services#IDatabaseService) Also adds regression tests for `UpdateLatest` and `Append`. (Might) fix Mario Kart 8 Deluxe crashing on first boot due to failed Mii verification check (due to custom Mii from emulator), and potentially Tomodachi Life: Living the Dream for the "Import Mii from system" option. Co-authored-by: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com> Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/43
This commit is contained in:
parent
8065dec744
commit
e8cc252d9a
5 changed files with 184 additions and 8 deletions
|
|
@ -81,8 +81,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, IStoredData<T> oldMiiData, SourceFlag flag, IStoredData<T> newMiiData) where T : unmanaged
|
||||
public ResultCode UpdateLatest<T>(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData<T>
|
||||
{
|
||||
newMiiData = default;
|
||||
|
||||
if (!flag.HasFlag(SourceFlag.Database))
|
||||
{
|
||||
return ResultCode.NotFound;
|
||||
|
|
@ -106,7 +108,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||
|
||||
newMiiData.SetFromStoreData(storeData);
|
||||
|
||||
if (oldMiiData == newMiiData)
|
||||
if (oldMiiData.Equals(newMiiData))
|
||||
{
|
||||
return ResultCode.NotUpdated;
|
||||
}
|
||||
|
|
@ -286,6 +288,18 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||
return result;
|
||||
}
|
||||
|
||||
public ResultCode Append(DatabaseSessionMetadata metadata, CharInfo charInfo)
|
||||
{
|
||||
ResultCode result = _miiDatabase.Append(metadata, _utilityImpl, charInfo);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
result = _miiDatabase.SaveDatabase();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData)
|
||||
{
|
||||
coreData = new CoreData();
|
||||
|
|
|
|||
|
|
@ -449,6 +449,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode Append(DatabaseSessionMetadata metadata, UtilityImpl utilityImpl, CharInfo charInfo)
|
||||
{
|
||||
if (!charInfo.IsValid())
|
||||
{
|
||||
return ResultCode.InvalidCharInfo;
|
||||
}
|
||||
|
||||
if (charInfo.Type == 1)
|
||||
{
|
||||
return ResultCode.InvalidOperationOnSpecialMii;
|
||||
}
|
||||
|
||||
CoreData coreData = new();
|
||||
coreData.SetFromCharInfo(charInfo);
|
||||
|
||||
StoreData storeData;
|
||||
|
||||
do
|
||||
{
|
||||
storeData = StoreData.BuildFromCoreData(utilityImpl, coreData);
|
||||
}
|
||||
while (_database.GetIndexByCreatorId(out _, storeData.CreateId));
|
||||
|
||||
return AddOrReplace(metadata, storeData);
|
||||
}
|
||||
|
||||
public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
|
||||
{
|
||||
if (!_database.GetIndexByCreatorId(out int index, createId))
|
||||
|
|
|
|||
|
|
@ -54,9 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||
|
||||
protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||
{
|
||||
newCharInfo = default;
|
||||
|
||||
return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo);
|
||||
return _database.UpdateLatest(_metadata, oldCharInfo, flag, out newCharInfo);
|
||||
}
|
||||
|
||||
protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo)
|
||||
|
|
@ -113,14 +111,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||
|
||||
protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData)
|
||||
{
|
||||
newStoreData = default;
|
||||
|
||||
if (!_isSystem)
|
||||
{
|
||||
newStoreData = default;
|
||||
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData);
|
||||
return _database.UpdateLatest(_metadata, oldStoreData, flag, out newStoreData);
|
||||
}
|
||||
|
||||
protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index)
|
||||
|
|
@ -262,5 +260,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||
{
|
||||
return _database.ConvertCharInfoToCoreData(charInfo, out coreData);
|
||||
}
|
||||
|
||||
protected override ResultCode Append(CharInfo charInfo)
|
||||
{
|
||||
return _database.Append(_metadata, charInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,6 +340,15 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||
return result;
|
||||
}
|
||||
|
||||
[CommandCmif(26)] // 10.2.0+
|
||||
// Append(nn::mii::CharInfo char_info)
|
||||
public ResultCode Append(ServiceCtx context)
|
||||
{
|
||||
CharInfo charInfo = context.RequestData.ReadStruct<CharInfo>();
|
||||
|
||||
return Append(charInfo);
|
||||
}
|
||||
|
||||
private Span<byte> CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
|
||||
{
|
||||
byte[] rawData;
|
||||
|
|
@ -421,5 +430,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.StaticService
|
|||
protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo);
|
||||
|
||||
protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData);
|
||||
|
||||
protected abstract ResultCode Append(CharInfo charInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
122
src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System.Reflection;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.StaticService;
|
||||
using Ryujinx.HLE.HOS.Services.Mii.Types;
|
||||
|
||||
namespace Ryujinx.Tests.HLE
|
||||
{
|
||||
public class MiiDatabaseTests
|
||||
{
|
||||
[Test]
|
||||
public void UpdateLatestReturnsStoredCharInfo()
|
||||
{
|
||||
DatabaseImpl database = new();
|
||||
StoreData storedData = StoreData.BuildDefault(new UtilityImpl(new TickSource(19200000)), 0);
|
||||
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||
|
||||
NintendoFigurineDatabase figurineDatabase = new();
|
||||
figurineDatabase.Format();
|
||||
figurineDatabase.Add(storedData);
|
||||
SetFigurineDatabase(databaseManager, figurineDatabase);
|
||||
|
||||
TestDatabaseService service = new(database);
|
||||
|
||||
CharInfo oldCharInfo = new();
|
||||
oldCharInfo.SetFromStoreData(storedData);
|
||||
oldCharInfo.Height--;
|
||||
|
||||
ResultCode result = service.UpdateLatestForTest(oldCharInfo, SourceFlag.Database, out CharInfo newCharInfo);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(newCharInfo.CreateId, Is.EqualTo(oldCharInfo.CreateId));
|
||||
Assert.That(newCharInfo.Height, Is.EqualTo(storedData.CoreData.Height));
|
||||
Assert.That(newCharInfo.IsValid(), Is.True);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendAddsRegularCharInfoToDatabase()
|
||||
{
|
||||
DatabaseImpl database = new();
|
||||
UtilityImpl utilityImpl = new(new TickSource(19200000));
|
||||
SetUtilityImpl(database, utilityImpl);
|
||||
MiiDatabaseManager databaseManager = GetDatabaseManager(database);
|
||||
SetFigurineDatabase(databaseManager, CreateFormattedDatabase());
|
||||
|
||||
StoreData defaultStoreData = StoreData.BuildDefault(utilityImpl, 0);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(defaultStoreData.CoreData.IsValid(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValidDataCrc(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValidDeviceCrc(), Is.True);
|
||||
Assert.That(defaultStoreData.IsValid(), Is.True);
|
||||
});
|
||||
|
||||
CharInfo charInfo = new();
|
||||
charInfo.SetFromStoreData(defaultStoreData);
|
||||
|
||||
DatabaseSessionMetadata metadata = database.CreateSessionMetadata(new SpecialMiiKeyCode());
|
||||
|
||||
ResultCode result = databaseManager.Append(metadata, utilityImpl, charInfo);
|
||||
|
||||
int count = databaseManager.GetCount(metadata);
|
||||
databaseManager.Get(metadata, 0, out StoreData storedData);
|
||||
|
||||
CoreData expectedCoreData = new();
|
||||
expectedCoreData.SetFromCharInfo(charInfo);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result, Is.EqualTo(ResultCode.Success));
|
||||
Assert.That(count, Is.EqualTo(1));
|
||||
Assert.That(storedData.IsValid(), Is.True);
|
||||
Assert.That(storedData.CreateId, Is.Not.EqualTo(charInfo.CreateId));
|
||||
Assert.That(storedData.CoreData, Is.EqualTo(expectedCoreData));
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestDatabaseService(DatabaseImpl database) : DatabaseServiceImpl(database, true, new SpecialMiiKeyCode())
|
||||
{
|
||||
public ResultCode UpdateLatestForTest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo)
|
||||
{
|
||||
return UpdateLatest(oldCharInfo, flag, out newCharInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static MiiDatabaseManager GetDatabaseManager(DatabaseImpl database)
|
||||
{
|
||||
return (MiiDatabaseManager)typeof(DatabaseImpl)
|
||||
.GetField("_miiDatabase", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.GetValue(database);
|
||||
}
|
||||
|
||||
private static void SetFigurineDatabase(MiiDatabaseManager databaseManager, NintendoFigurineDatabase figurineDatabase)
|
||||
{
|
||||
typeof(MiiDatabaseManager)
|
||||
.GetField("_database", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(databaseManager, figurineDatabase);
|
||||
}
|
||||
|
||||
private static NintendoFigurineDatabase CreateFormattedDatabase()
|
||||
{
|
||||
NintendoFigurineDatabase figurineDatabase = new();
|
||||
figurineDatabase.Format();
|
||||
|
||||
return figurineDatabase;
|
||||
}
|
||||
|
||||
private static void SetUtilityImpl(DatabaseImpl database, UtilityImpl utilityImpl)
|
||||
{
|
||||
typeof(DatabaseImpl)
|
||||
.GetField("_utilityImpl", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(database, utilityImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue