diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index ae3e247d..cbd07a15 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -137,5 +137,6 @@ public static Result SubStorageNotInitialized => new Result(ModuleFs, 6902); public static Result MountNameNotFound => new Result(ModuleFs, 6905); + public static Result Result6906 => new Result(ModuleFs, 6906); } } diff --git a/src/LibHac/Fs/ResultRangeFs.cs b/src/LibHac/Fs/ResultRangeFs.cs index f0289aeb..a89ba8eb 100644 --- a/src/LibHac/Fs/ResultRangeFs.cs +++ b/src/LibHac/Fs/ResultRangeFs.cs @@ -12,6 +12,7 @@ public static ResultRange FatFsCorrupted => new ResultRange(ResultFs.ModuleFs, 4681, 4699); public static ResultRange HostFsCorrupted => new ResultRange(ResultFs.ModuleFs, 4701, 4719); public static ResultRange FileTableCorrupted => new ResultRange(ResultFs.ModuleFs, 4721, 4739); + public static ResultRange Range4771To4779 => new ResultRange(ResultFs.ModuleFs, 4771, 4779); public static ResultRange Range4811To4819 => new ResultRange(ResultFs.ModuleFs, 4811, 4819); public static ResultRange UnexpectedFailure => new ResultRange(ResultFs.ModuleFs, 5000, 5999); diff --git a/src/LibHac/Fs/SaveData.cs b/src/LibHac/Fs/SaveData.cs index a0bc1b21..a612c824 100644 --- a/src/LibHac/Fs/SaveData.cs +++ b/src/LibHac/Fs/SaveData.cs @@ -1,10 +1,68 @@ -using LibHac.Common; +using System; +using LibHac.Common; using LibHac.FsService; +using LibHac.FsSystem.Save; +using LibHac.Ncm; namespace LibHac.Fs { public static class SaveData { + public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, TitleId titleId, UserId userId) + { + Result rc; + + if (fs.IsEnabledAccessLog(LocalAccessLogMode.Application)) + { + TimeSpan startTime = fs.Time.GetCurrent(); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0); + TimeSpan endTime = fs.Time.GetCurrent(); + + fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{titleId}, userid: {userId}"); + } + else + { + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, titleId, userId, SaveDataType.SaveData, false, 0); + } + + if (rc.IsSuccess() && fs.IsEnabledAccessLog(LocalAccessLogMode.Application)) + { + fs.EnableFileSystemAccessorAccessLog(mountName); + } + + return rc; + } + + private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, + TitleId titleId, UserId userId, SaveDataType type, bool openReadOnly, short index) + { + Result rc = MountHelpers.CheckMountName(mountName); + if (rc.IsFailure()) return rc; + + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + SaveDataAttribute attribute = default; + attribute.TitleId = titleId; + attribute.UserId = userId; + attribute.Type = type; + attribute.Index = index; + + IFileSystem saveFs; + + if (openReadOnly) + { + rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, ref attribute); + } + else + { + rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, ref attribute); + } + + if (rc.IsFailure()) return rc; + + return fs.Register(mountName, saveFs); + } + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId) { return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero); @@ -98,6 +156,17 @@ namespace LibHac.Fs () => $", savedataid: 0x{saveDataId:X}"); } + public static Result DeleteSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId) + { + return fs.RunOperationWithAccessLog(LocalAccessLogMode.System, + () => + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + return fsProxy.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + }, + () => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}"); + } + public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient) { IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject(); diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index 62fb2856..914cf094 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -9,7 +9,7 @@ namespace LibHac.Fs [StructLayout(LayoutKind.Explicit, Size = 0x40)] public struct SaveDataAttribute : IEquatable, IComparable { - [FieldOffset(0x00)] public ulong TitleId; + [FieldOffset(0x00)] public TitleId TitleId; [FieldOffset(0x08)] public UserId UserId; [FieldOffset(0x18)] public ulong SaveDataId; [FieldOffset(0x20)] public SaveDataType Type; @@ -71,7 +71,7 @@ namespace LibHac.Fs [FieldOffset(0x04)] public bool FilterByIndex; [FieldOffset(0x05)] public byte Rank; - [FieldOffset(0x08)] public TitleId TitleID; + [FieldOffset(0x08)] public TitleId TitleId; [FieldOffset(0x10)] public UserId UserId; [FieldOffset(0x20)] public ulong SaveDataId; [FieldOffset(0x28)] public SaveDataType SaveDataType; diff --git a/src/LibHac/Fs/UserId.cs b/src/LibHac/Fs/UserId.cs index 31db1dec..dd1f4761 100644 --- a/src/LibHac/Fs/UserId.cs +++ b/src/LibHac/Fs/UserId.cs @@ -25,7 +25,7 @@ namespace LibHac.Fs public override string ToString() { - return $"0x{Id.High:x8}{Id.Low:x8}"; + return $"0x{Id.High:X16}{Id.Low:X16}"; } public bool Equals(UserId other) => Id == other.Id; diff --git a/src/LibHac/FsService/FileSystemProxy.cs b/src/LibHac/FsService/FileSystemProxy.cs index 9b18e9e0..9a1c4995 100644 --- a/src/LibHac/FsService/FileSystemProxy.cs +++ b/src/LibHac/FsService/FileSystemProxy.cs @@ -2,6 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.FsSystem.Save; using LibHac.Ncm; using LibHac.Spl; @@ -14,6 +15,7 @@ namespace LibHac.FsService /// The client instance to be used for internal operations like save indexer access. // ReSharper disable once UnusedAutoPropertyAccessor.Local private FileSystemClient FsClient { get; } + private FileSystemServer FsServer { get; } public long CurrentProcess { get; private set; } @@ -24,10 +26,11 @@ namespace LibHac.FsService private const ulong SaveIndexerId = 0x8000000000000000; - internal FileSystemProxy(FileSystemProxyCore fsProxyCore, FileSystemClient fsClient) + internal FileSystemProxy(FileSystemProxyCore fsProxyCore, FileSystemClient fsClient, FileSystemServer fsServer) { FsProxyCore = fsProxyCore; FsClient = fsClient; + FsServer = fsServer; CurrentProcess = -1; SaveDataSize = 0x2000000; @@ -147,6 +150,9 @@ namespace LibHac.FsService private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) { + fileSystem = default; + saveDataId = default; + bool hasFixedId = attribute.SaveDataId != 0 && attribute.UserId == UserId.Zero; if (hasFixedId) @@ -155,7 +161,25 @@ namespace LibHac.FsService } else { - throw new NotImplementedException(); + SaveDataAttribute indexerKey = attribute; + + Result rc = FsServer.SaveDataIndexerManager.GetSaveDataIndexer(out SaveDataIndexerReader reader, spaceId); + using SaveDataIndexerReader c = reader; + if (rc.IsFailure()) return rc; + + c.Indexer.Get(out SaveDataIndexerValue indexerValue, ref indexerKey); + + SaveDataSpaceId indexerSpaceId = spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.Safe + ? SaveDataSpaceId.System + : spaceId; + + if (indexerValue.SpaceId != indexerSpaceId) + return ResultFs.TargetNotFound.Log(); + + if (indexerValue.State == 4) + return ResultFs.Result6906.Log(); + + saveDataId = indexerValue.SaveDataId; } Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId, @@ -167,7 +191,6 @@ namespace LibHac.FsService if (saveDataId != SaveIndexerId) { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (hasFixedId) { // todo: remove save indexer entry @@ -177,15 +200,61 @@ namespace LibHac.FsService return ResultFs.TargetNotFound; } + private Result OpenSaveDataFileSystem3(out IFileSystem fileSystem, SaveDataSpaceId spaceId, + ref SaveDataAttribute attribute, bool openReadOnly) + { + // Missing check if the open limit has been hit + + Result rc = OpenSaveDataFileSystemImpl(out fileSystem, out _, spaceId, ref attribute, openReadOnly, true); + + // Missing permission check based on the save's owner ID, + // speed emulation storage type wrapper, and FileSystemInterfaceAdapter + + return rc; + } + + private Result OpenSaveDataFileSystem2(out IFileSystem fileSystem, SaveDataSpaceId spaceId, + ref SaveDataAttribute attribute, bool openReadOnly) + { + fileSystem = default; + + // Missing permission checks + + SaveDataAttribute attributeCopy; + + if (attribute.TitleId == TitleId.Zero) + { + throw new NotImplementedException(); + } + else + { + attributeCopy = attribute; + } + + SaveDataSpaceId actualSpaceId; + + if (attributeCopy.Type == SaveDataType.CacheStorage) + { + // Check whether the save is on the SD card or the BIS + throw new NotImplementedException(); + } + else + { + actualSpaceId = spaceId; + } + + return OpenSaveDataFileSystem3(out fileSystem, actualSpaceId, ref attributeCopy, openReadOnly); + } + public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute) { - throw new NotImplementedException(); + return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, false); } public Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute) { - throw new NotImplementedException(); + return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, true); } public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, diff --git a/src/LibHac/FsService/FileSystemServer.cs b/src/LibHac/FsService/FileSystemServer.cs index c1ae6de2..fee81bb5 100644 --- a/src/LibHac/FsService/FileSystemServer.cs +++ b/src/LibHac/FsService/FileSystemServer.cs @@ -6,22 +6,26 @@ namespace LibHac.FsService { public class FileSystemServer { + internal const ulong SaveDataIndexerSaveId = 0x8000000000000000; + private FileSystemProxyCore FsProxyCore { get; } /// The client instance to be used for internal operations like save indexer access. private FileSystemClient FsClient { get; } private ITimeSpanGenerator Timer { get; } - + + internal SaveDataIndexerManager SaveDataIndexerManager { get; } + /// /// Creates a new . /// /// The configuration for the created . public FileSystemServer(FileSystemServerConfig config) { - if(config.FsCreators == null) + if (config.FsCreators == null) throw new ArgumentException("FsCreators must not be null"); - if(config.DeviceOperator == null) + if (config.DeviceOperator == null) throw new ArgumentException("DeviceOperator must not be null"); ExternalKeySet externalKeySet = config.ExternalKeySet ?? new ExternalKeySet(); @@ -30,6 +34,8 @@ namespace LibHac.FsService FsProxyCore = new FileSystemProxyCore(config.FsCreators, externalKeySet, config.DeviceOperator); FsClient = new FileSystemClient(this, timer); Timer = timer; + + SaveDataIndexerManager = new SaveDataIndexerManager(FsClient, SaveDataIndexerSaveId); } /// @@ -52,7 +58,7 @@ namespace LibHac.FsService public IFileSystemProxy CreateFileSystemProxyService() { - return new FileSystemProxy(FsProxyCore, FsClient); + return new FileSystemProxy(FsProxyCore, FsClient, this); } } diff --git a/src/LibHac/FsService/ISaveDataIndexer.cs b/src/LibHac/FsService/ISaveDataIndexer.cs new file mode 100644 index 00000000..cb5ee01a --- /dev/null +++ b/src/LibHac/FsService/ISaveDataIndexer.cs @@ -0,0 +1,9 @@ +using LibHac.Fs; + +namespace LibHac.FsService +{ + public interface ISaveDataIndexer + { + Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key); + } +} \ No newline at end of file diff --git a/src/LibHac/FsService/SaveDataIndexer.cs b/src/LibHac/FsService/SaveDataIndexer.cs new file mode 100644 index 00000000..9b513938 --- /dev/null +++ b/src/LibHac/FsService/SaveDataIndexer.cs @@ -0,0 +1,219 @@ +using System.Diagnostics; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Kvdb; + +namespace LibHac.FsService +{ + public class SaveDataIndexer : ISaveDataIndexer + { + private const string LastIdFileName = "lastPublishedId"; + private const long LastIdFileSize = 8; + + private FileSystemClient FsClient { get; } + private string MountName { get; } + private ulong SaveDataId { get; } + private SaveDataSpaceId SpaceId { get; } + private KeyValueDatabase KvDatabase { get; set; } + private object Locker { get; } = new object(); + private bool IsInitialized { get; set; } + private bool IsKvdbLoaded { get; set; } + private long LastPublishedId { get; set; } + + public SaveDataIndexer(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, ulong saveDataId) + { + FsClient = fsClient; + MountName = mountName; + SaveDataId = saveDataId; + SpaceId = spaceId; + } + + public Result Get(out SaveDataIndexerValue value, ref SaveDataAttribute key) + { + value = default; + + lock (Locker) + { + Result rc = Initialize(); + if (rc.IsFailure()) return rc; + + rc = EnsureKvDatabaseLoaded(false); + if (rc.IsFailure()) return rc; + + rc = KvDatabase.Get(ref key, SpanHelpers.AsByteSpan(ref value)); + + if (rc.IsFailure()) + { + return ResultFs.TargetNotFound.LogConverted(rc); + } + + return Result.Success; + } + } + + private Result Initialize() + { + if (IsInitialized) return Result.Success; + + var mount = new Mounter(); + + try + { + Result rc = mount.Initialize(FsClient, MountName, SpaceId, SaveDataId); + if (rc.IsFailure()) return rc; + + string dbDirectory = $"{MountName}:/"; + + rc = FsClient.GetEntryType(out DirectoryEntryType entryType, dbDirectory); + if (rc.IsFailure()) return rc; + + if (entryType == DirectoryEntryType.File) + return ResultFs.PathNotFound.Log(); + + string dbArchiveFile = $"{dbDirectory}imkvdb.arc"; + + KvDatabase = new KeyValueDatabase(FsClient, dbArchiveFile); + + IsInitialized = true; + return Result.Success; + } + finally + { + mount.Dispose(); + } + } + + private Result EnsureKvDatabaseLoaded(bool forceLoad) + { + Debug.Assert(KvDatabase != null); + + if (forceLoad) + { + IsKvdbLoaded = false; + } + else if (IsKvdbLoaded) + { + return Result.Success; + } + + var mount = new Mounter(); + + try + { + Result rc = mount.Initialize(FsClient, MountName, SpaceId, SaveDataId); + if (rc.IsFailure()) return rc; + + rc = KvDatabase.ReadDatabaseFromFile(); + if (rc.IsFailure()) return rc; + + bool createdNewFile = false; + string idFilePath = $"{MountName}:/{LastIdFileName}"; + + rc = FsClient.OpenFile(out FileHandle handle, idFilePath, OpenMode.Read); + + if (rc.IsFailure()) + { + if (rc != ResultFs.PathNotFound) return rc; + + rc = FsClient.CreateFile(idFilePath, LastIdFileSize); + if (rc.IsFailure()) return rc; + + rc = FsClient.OpenFile(out handle, idFilePath, OpenMode.Read); + if (rc.IsFailure()) return rc; + + createdNewFile = true; + + LastPublishedId = 0; + IsKvdbLoaded = true; + } + + try + { + if (!createdNewFile) + { + long lastId = default; + + rc = FsClient.ReadFile(handle, 0, SpanHelpers.AsByteSpan(ref lastId)); + if (rc.IsFailure()) return rc; + + LastPublishedId = lastId; + IsKvdbLoaded = true; + } + + return Result.Success; + } + finally + { + FsClient.CloseFile(handle); + + if (createdNewFile) + { + FsClient.Commit(MountName); + } + } + } + finally + { + mount.Dispose(); + } + } + + private ref struct Mounter + { + private FileSystemClient FsClient { get; set; } + private string MountName { get; set; } + private bool IsMounted { get; set; } + + public Result Initialize(FileSystemClient fsClient, string mountName, SaveDataSpaceId spaceId, + ulong saveDataId) + { + FsClient = fsClient; + MountName = mountName; + + FsClient.DisableAutoSaveDataCreation(); + + Result rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); + + if (rc.IsFailure()) + { + if (rc == ResultFs.TargetNotFound) + { + rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); + if (rc.IsFailure()) return rc; + + rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + } + else + { + if (ResultRangeFs.Range4771To4779.Contains(rc)) return rc; + if (!ResultRangeFs.DataCorrupted.Contains(rc)) return rc; + + if (spaceId == SaveDataSpaceId.SdSystem) return rc; + + rc = FsClient.DeleteSaveData(spaceId, saveDataId); + if (rc.IsFailure()) return rc; + + rc = FsClient.CreateSystemSaveData(spaceId, saveDataId, 0, 0xC0000, 0xC0000, 0); + if (rc.IsFailure()) return rc; + + rc = FsClient.MountSystemSaveData(MountName.ToU8Span(), spaceId, saveDataId); + if (rc.IsFailure()) return rc; + } + } + + IsMounted = true; + + return Result.Success; + } + + public void Dispose() + { + if (IsMounted) + { + FsClient.Unmount(MountName); + } + } + } + } +} diff --git a/src/LibHac/FsService/SaveDataIndexerManager.cs b/src/LibHac/FsService/SaveDataIndexerManager.cs new file mode 100644 index 00000000..74604ae1 --- /dev/null +++ b/src/LibHac/FsService/SaveDataIndexerManager.cs @@ -0,0 +1,115 @@ +using System; +using System.Threading; +using LibHac.Fs; + +namespace LibHac.FsService +{ + internal class SaveDataIndexerManager + { + private FileSystemClient FsClient { get; } + private ulong SaveDataId { get; } + + private IndexerHolder _bisIndexer = new IndexerHolder(new object()); + private IndexerHolder _sdCardIndexer = new IndexerHolder(new object()); + private IndexerHolder _safeIndexer = new IndexerHolder(new object()); + private IndexerHolder _properSystemIndexer = new IndexerHolder(new object()); + + public SaveDataIndexerManager(FileSystemClient fsClient, ulong saveDataId) + { + FsClient = fsClient; + SaveDataId = saveDataId; + } + + public Result GetSaveDataIndexer(out SaveDataIndexerReader reader, SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.User: + Monitor.Enter(_bisIndexer.Locker); + + if (!_bisIndexer.IsInitialized) + { + _bisIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDb", SaveDataSpaceId.System, SaveDataId); + } + + reader = new SaveDataIndexerReader(_bisIndexer.Indexer, _bisIndexer.Locker); + return Result.Success; + + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: + Monitor.Enter(_sdCardIndexer.Locker); + + // Missing reinitialize if SD handle is old + + if (!_sdCardIndexer.IsInitialized) + { + _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSd", SaveDataSpaceId.SdSystem, SaveDataId); + } + + reader = new SaveDataIndexerReader(_bisIndexer.Indexer, _bisIndexer.Locker); + return Result.Success; + + case SaveDataSpaceId.TemporaryStorage: + throw new NotImplementedException(); + + case SaveDataSpaceId.ProperSystem: + Monitor.Enter(_safeIndexer.Locker); + + if (!_safeIndexer.IsInitialized) + { + _safeIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbPr", SaveDataSpaceId.ProperSystem, SaveDataId); + } + + reader = new SaveDataIndexerReader(_safeIndexer.Indexer, _safeIndexer.Locker); + return Result.Success; + + case SaveDataSpaceId.Safe: + Monitor.Enter(_properSystemIndexer.Locker); + + if (!_properSystemIndexer.IsInitialized) + { + _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, "saveDataIxrDbSf", SaveDataSpaceId.Safe, SaveDataId); + } + + reader = new SaveDataIndexerReader(_properSystemIndexer.Indexer, _properSystemIndexer.Locker); + return Result.Success; + + default: + reader = default; + return ResultFs.InvalidArgument.Log(); + } + } + + private struct IndexerHolder + { + public object Locker { get; } + public ISaveDataIndexer Indexer { get; set; } + + public IndexerHolder(object locker) + { + Locker = locker; + Indexer = null; + } + + public bool IsInitialized => Indexer != null; + } + } + + public ref struct SaveDataIndexerReader + { + private object Locker; + public ISaveDataIndexer Indexer; + + internal SaveDataIndexerReader(ISaveDataIndexer indexer, object locker) + { + Locker = locker; + Indexer = indexer; + } + + public void Dispose() + { + Monitor.Exit(Locker); + } + } +} diff --git a/src/LibHac/FsService/SaveDataIndexerEntry.cs b/src/LibHac/FsService/SaveDataIndexerValue.cs similarity index 91% rename from src/LibHac/FsService/SaveDataIndexerEntry.cs rename to src/LibHac/FsService/SaveDataIndexerValue.cs index 80283776..864eb3d4 100644 --- a/src/LibHac/FsService/SaveDataIndexerEntry.cs +++ b/src/LibHac/FsService/SaveDataIndexerValue.cs @@ -4,7 +4,7 @@ using LibHac.Fs; namespace LibHac.FsService { [StructLayout(LayoutKind.Explicit, Size = 0x40)] - public struct SaveDataIndexerEntry + public struct SaveDataIndexerValue { [FieldOffset(0x00)] public ulong SaveDataId; [FieldOffset(0x08)] public ulong Size; diff --git a/src/LibHac/Ncm/TitleId.cs b/src/LibHac/Ncm/TitleId.cs index 0288bd7b..1e0e8514 100644 --- a/src/LibHac/Ncm/TitleId.cs +++ b/src/LibHac/Ncm/TitleId.cs @@ -1,10 +1,13 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; namespace LibHac.Ncm { [DebuggerDisplay("{" + nameof(Value) + "}")] - public struct TitleId + public struct TitleId : IEquatable, IComparable, IComparable { + public static TitleId Zero => default; + public readonly ulong Value; public TitleId(ulong value) @@ -13,5 +16,20 @@ namespace LibHac.Ncm } public static explicit operator ulong(TitleId titleId) => titleId.Value; + + public override string ToString() => $"{Value:X16}"; + + public int CompareTo(object obj) + { + if (ReferenceEquals(null, obj)) return 1; + return obj is TitleId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(TitleId)}"); + } + + public int CompareTo(TitleId other) => Value.CompareTo(other.Value); + public bool Equals(TitleId other) => Value == other.Value; + public override bool Equals(object obj) => obj is TitleId other && Equals(other); + public override int GetHashCode() => Value.GetHashCode(); + public static bool operator ==(TitleId left, TitleId right) => left.Equals(right); + public static bool operator !=(TitleId left, TitleId right) => !left.Equals(right); } } diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index 33c2875d..d9fa6cf6 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -45,6 +45,16 @@ namespace LibHac return this; } + /// + /// Same as , but for when one result is converted to another. + /// + /// The original value. + /// The called value. + public Result LogConverted(Result originalResult) + { + return this; + } + public override string ToString() { return IsSuccess() ? "Success" : ErrorCode;