aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
new file mode 100644
index 00000000..682283b0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs
@@ -0,0 +1,501 @@
+using LibHac;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.Fs.Shim;
+using LibHac.Ncm;
+using Ryujinx.HLE.HOS.Services.Mii.Types;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Services.Mii
+{
+ class MiiDatabaseManager
+ {
+ private static bool IsTestModeEnabled = false;
+ private static uint MountCounter = 0;
+
+ private const ulong DatabaseTestSaveDataId = 0x8000000000000031;
+ private const ulong DatabaseSaveDataId = 0x8000000000000030;
+
+ private static U8String DatabasePath = new U8String("mii:/MiiDatabase.dat");
+ private static U8String MountName = new U8String("mii");
+
+ private NintendoFigurineDatabase _database;
+ private bool _isDirty;
+
+ private HorizonClient _horizonClient;
+
+ protected ulong UpdateCounter { get; private set; }
+
+ public MiiDatabaseManager()
+ {
+ _database = new NintendoFigurineDatabase();
+ _isDirty = false;
+ UpdateCounter = 0;
+ }
+
+ private void ResetDatabase()
+ {
+ _database = new NintendoFigurineDatabase();
+ _database.Format();
+ }
+
+ private void MarkDirty(DatabaseSessionMetadata metadata)
+ {
+ _isDirty = true;
+
+ UpdateCounter++;
+
+ metadata.UpdateCounter = UpdateCounter;
+ }
+
+ private bool GetAtVirtualIndex(int index, out int realIndex, out StoreData storeData)
+ {
+ realIndex = -1;
+ storeData = new StoreData();
+
+ int virtualIndex = 0;
+
+ for (int i = 0; i < _database.Length; i++)
+ {
+ StoreData tmp = _database.Get(i);
+
+ if (!tmp.IsSpecial())
+ {
+ if (index == virtualIndex)
+ {
+ realIndex = i;
+ storeData = tmp;
+
+ return true;
+ }
+
+ virtualIndex++;
+ }
+ }
+
+ return false;
+ }
+
+ private int ConvertRealIndexToVirtualIndex(int realIndex)
+ {
+ int virtualIndex = 0;
+
+ for (int i = 0; i < realIndex; i++)
+ {
+ StoreData tmp = _database.Get(i);
+
+ if (!tmp.IsSpecial())
+ {
+ virtualIndex++;
+ }
+ }
+
+ return virtualIndex;
+ }
+
+ public void InitializeDatabase(HorizonClient horizonClient)
+ {
+ _horizonClient = horizonClient;
+
+ // Ensure we have valid data in the database
+ _database.Format();
+
+ MountSave();
+ }
+
+ private Result MountSave()
+ {
+ if (MountCounter != 0)
+ {
+ MountCounter++;
+ return Result.Success;
+ }
+
+ ulong saveDataId = IsTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId;
+
+ Result result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId);
+
+ if (result.IsFailure())
+ {
+ if (!ResultFs.TargetNotFound.Includes(result))
+ return result;
+
+ if (IsTestModeEnabled)
+ {
+ result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000,
+ SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
+ if (result.IsFailure()) return result;
+ }
+ else
+ {
+ result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000,
+ 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
+ if (result.IsFailure()) return result;
+ }
+
+ result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId);
+ if (result.IsFailure()) return result;
+ }
+
+ if (result == Result.Success)
+ {
+ MountCounter++;
+ }
+ return result;
+ }
+
+ public ResultCode DeleteFile()
+ {
+ ResultCode result = (ResultCode)_horizonClient.Fs.DeleteFile(DatabasePath).Value;
+
+ _horizonClient.Fs.Commit(MountName);
+
+ return result;
+ }
+
+ public ResultCode LoadFromFile(out bool isBroken)
+ {
+ isBroken = false;
+
+ if (MountCounter == 0)
+ {
+ return ResultCode.InvalidArgument;
+ }
+
+ UpdateCounter++;
+
+ ResetDatabase();
+
+ Result result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read);
+
+ if (result.IsSuccess())
+ {
+ result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
+
+ if (result.IsSuccess())
+ {
+ if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())
+ {
+ result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan());
+
+ if (result.IsSuccess())
+ {
+ if (_database.Verify() != ResultCode.Success)
+ {
+ ResetDatabase();
+
+ isBroken = true;
+ }
+ else
+ {
+ isBroken = _database.FixDatabase();
+ }
+ }
+ }
+ else
+ {
+ isBroken = true;
+ }
+ }
+
+ _horizonClient.Fs.CloseFile(handle);
+
+ return (ResultCode)result.Value;
+ }
+ else if (ResultFs.PathNotFound.Includes(result))
+ {
+ return (ResultCode)ForceSaveDatabase().Value;
+ }
+
+ return ResultCode.Success;
+ }
+
+ private Result ForceSaveDatabase()
+ {
+ Result result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
+
+ if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result))
+ {
+ result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write);
+
+ if (result.IsSuccess())
+ {
+ result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
+
+ if (result.IsSuccess())
+ {
+ // If the size doesn't match, recreate the file
+ if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>())
+ {
+ _horizonClient.Fs.CloseFile(handle);
+
+ result = _horizonClient.Fs.DeleteFile(DatabasePath);
+
+ if (result.IsSuccess())
+ {
+ result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
+
+ if (result.IsSuccess())
+ {
+ result = _horizonClient.Fs.OpenFile(out handle, DatabasePath, OpenMode.Write);
+ }
+ }
+
+ if (result.IsFailure())
+ {
+ return result;
+ }
+ }
+
+ result = _horizonClient.Fs.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush);
+ }
+
+ _horizonClient.Fs.CloseFile(handle);
+ }
+ }
+
+ if (result.IsSuccess())
+ {
+ _isDirty = false;
+
+ result = _horizonClient.Fs.Commit(MountName);
+ }
+
+ return result;
+ }
+
+ public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode)
+ {
+ return new DatabaseSessionMetadata(UpdateCounter, miiKeyCode);
+ }
+
+ public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion)
+ {
+ metadata.InterfaceVersion = interfaceVersion;
+ }
+
+ public bool IsUpdated(DatabaseSessionMetadata metadata)
+ {
+ bool result = metadata.UpdateCounter != UpdateCounter;
+
+ metadata.UpdateCounter = UpdateCounter;
+
+ return result;
+ }
+
+ public int GetCount(DatabaseSessionMetadata metadata)
+ {
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
+ {
+ int count = 0;
+
+ for (int i = 0; i < _database.Length; i++)
+ {
+ StoreData tmp = _database.Get(i);
+
+ if (!tmp.IsSpecial())
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+ else
+ {
+ return _database.Length;
+ }
+ }
+
+ public void Get(DatabaseSessionMetadata metadata, int index, out StoreData storeData)
+ {
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
+ {
+ if (GetAtVirtualIndex(index, out int realIndex, out _))
+ {
+ index = realIndex;
+ }
+ else
+ {
+ index = 0;
+ }
+ }
+
+ storeData = _database.Get(index);
+ }
+
+ public ResultCode FindIndex(DatabaseSessionMetadata metadata, out int index, CreateId createId)
+ {
+ return FindIndex(out index, createId, metadata.MiiKeyCode.IsEnabledSpecialMii());
+ }
+
+ public ResultCode FindIndex(out int index, CreateId createId, bool isSpecial)
+ {
+ if (_database.GetIndexByCreatorId(out int realIndex, createId))
+ {
+ if (isSpecial)
+ {
+ index = realIndex;
+
+ return ResultCode.Success;
+ }
+
+ StoreData storeData = _database.Get(realIndex);
+
+ if (!storeData.IsSpecial())
+ {
+ if (realIndex < 1)
+ {
+ index = 0;
+ }
+ else
+ {
+ index = ConvertRealIndexToVirtualIndex(realIndex);
+ }
+
+ return ResultCode.Success;
+ }
+ }
+
+ index = -1;
+
+ return ResultCode.NotFound;
+ }
+
+ public ResultCode Move(DatabaseSessionMetadata metadata, int newIndex, CreateId createId)
+ {
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
+ {
+ if (GetAtVirtualIndex(newIndex, out int realIndex, out _))
+ {
+ newIndex = realIndex;
+ }
+ else
+ {
+ newIndex = 0;
+ }
+ }
+
+ if (_database.GetIndexByCreatorId(out int oldIndex, createId))
+ {
+ StoreData realStoreData = _database.Get(oldIndex);
+
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && realStoreData.IsSpecial())
+ {
+ return ResultCode.InvalidOperationOnSpecialMii;
+ }
+
+ ResultCode result = _database.Move(newIndex, oldIndex);
+
+ if (result == ResultCode.Success)
+ {
+ MarkDirty(metadata);
+ }
+
+ return result;
+ }
+
+ return ResultCode.NotFound;
+ }
+
+ public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData)
+ {
+ if (!storeData.IsValid())
+ {
+ return ResultCode.InvalidStoreData;
+ }
+
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && storeData.IsSpecial())
+ {
+ return ResultCode.InvalidOperationOnSpecialMii;
+ }
+
+ if (_database.GetIndexByCreatorId(out int index, storeData.CreateId))
+ {
+ StoreData oldStoreData = _database.Get(index);
+
+ if (oldStoreData.IsSpecial())
+ {
+ return ResultCode.InvalidOperationOnSpecialMii;
+ }
+
+ _database.Replace(index, storeData);
+ }
+ else
+ {
+ if (_database.IsFull())
+ {
+ return ResultCode.DatabaseFull;
+ }
+
+ _database.Add(storeData);
+ }
+
+ MarkDirty(metadata);
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId)
+ {
+ if (!_database.GetIndexByCreatorId(out int index, createId))
+ {
+ return ResultCode.NotFound;
+ }
+
+ if (!metadata.MiiKeyCode.IsEnabledSpecialMii())
+ {
+ StoreData storeData = _database.Get(index);
+
+ if (storeData.IsSpecial())
+ {
+ return ResultCode.InvalidOperationOnSpecialMii;
+ }
+ }
+
+ _database.Delete(index);
+
+ MarkDirty(metadata);
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode DestroyFile(DatabaseSessionMetadata metadata)
+ {
+ _database.CorruptDatabase();
+
+ MarkDirty(metadata);
+
+ ResultCode result = SaveDatabase();
+
+ ResetDatabase();
+
+ return result;
+ }
+
+ public ResultCode SaveDatabase()
+ {
+ if (_isDirty)
+ {
+ return (ResultCode)ForceSaveDatabase().Value;
+ }
+ else
+ {
+ return ResultCode.NotUpdated;
+ }
+ }
+
+ public void FormatDatabase(DatabaseSessionMetadata metadata)
+ {
+ _database.Format();
+
+ MarkDirty(metadata);
+ }
+
+ public bool IsFullDatabase()
+ {
+ return _database.IsFull();
+ }
+ }
+}