diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs index 4bb736992..cd2115132 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs @@ -81,8 +81,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii return ResultCode.Success; } - public ResultCode UpdateLatest(DatabaseSessionMetadata metadata, IStoredData oldMiiData, SourceFlag flag, IStoredData newMiiData) where T : unmanaged + public ResultCode UpdateLatest(DatabaseSessionMetadata metadata, T oldMiiData, SourceFlag flag, out T newMiiData) where T : unmanaged, IStoredData { + 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(); diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs index 356d42a85..f5a173d8e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs @@ -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)) diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs index fc12e2533..c12517430 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs @@ -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); + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs index 1a1c20d6e..3f9fad4fb 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs @@ -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(); + + return Append(charInfo); + } + private Span 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); } } diff --git a/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs b/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs new file mode 100644 index 000000000..b1a20bb93 --- /dev/null +++ b/src/Ryujinx.Tests/HLE/MiiDatabaseTests.cs @@ -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); + } + } +}