mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add IMKV database reader and writer (#61)
* Add Result struct * Add IMKV database reading * Add imkvdb writing * Add get and set to kvdb * Add Freeze method to IExportable * Add generic kvdb value * Add ContentMetaKey for use with kvdb
This commit is contained in:
parent
a7391afa9c
commit
dc8aad1e71
12 changed files with 630 additions and 0 deletions
95
src/LibHac/Fs/SaveDataStruct.cs
Normal file
95
src/LibHac/Fs/SaveDataStruct.cs
Normal file
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using LibHac.Fs.Save;
|
||||
using LibHac.Kvdb;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SaveDataStruct : IComparable<SaveDataStruct>, IComparable, IEquatable<SaveDataStruct>, IExportable
|
||||
{
|
||||
public ulong TitleId { get; private set; }
|
||||
public UserId UserId { get; private set; }
|
||||
public ulong SaveId { get; private set; }
|
||||
public SaveDataType Type { get; private set; }
|
||||
public byte Rank { get; private set; }
|
||||
public short Index { get; private set; }
|
||||
|
||||
public int ExportSize => 0x40;
|
||||
private bool _isFrozen;
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
|
||||
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(output, TitleId);
|
||||
UserId.ToBytes(output.Slice(8));
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(output.Slice(0x18), SaveId);
|
||||
output[0x20] = (byte)Type;
|
||||
output[0x21] = Rank;
|
||||
BinaryPrimitives.WriteInt16LittleEndian(output.Slice(0x22), Index);
|
||||
}
|
||||
|
||||
public void FromBytes(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
|
||||
if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short.");
|
||||
|
||||
TitleId = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||
UserId = new UserId(input.Slice(8));
|
||||
SaveId = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(0x18));
|
||||
Type = (SaveDataType)input[0x20];
|
||||
Rank = input[0x21];
|
||||
Index = BinaryPrimitives.ReadInt16LittleEndian(input.Slice(0x22));
|
||||
}
|
||||
|
||||
public void Freeze() => _isFrozen = true;
|
||||
|
||||
public bool Equals(SaveDataStruct other)
|
||||
{
|
||||
return other != null && TitleId == other.TitleId && UserId.Equals(other.UserId) && SaveId == other.SaveId &&
|
||||
Type == other.Type && Rank == other.Rank && Index == other.Index;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SaveDataStruct other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// ReSharper disable NonReadonlyMemberInGetHashCode
|
||||
int hashCode = TitleId.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ UserId.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ SaveId.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ (int)Type;
|
||||
hashCode = (hashCode * 397) ^ Rank.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Index.GetHashCode();
|
||||
return hashCode;
|
||||
// ReSharper restore NonReadonlyMemberInGetHashCode
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(SaveDataStruct other)
|
||||
{
|
||||
int titleIdComparison = TitleId.CompareTo(other.TitleId);
|
||||
if (titleIdComparison != 0) return titleIdComparison;
|
||||
int typeComparison = Type.CompareTo(other.Type);
|
||||
if (typeComparison != 0) return typeComparison;
|
||||
int userIdComparison = UserId.CompareTo(other.UserId);
|
||||
if (userIdComparison != 0) return userIdComparison;
|
||||
int saveIdComparison = SaveId.CompareTo(other.SaveId);
|
||||
if (saveIdComparison != 0) return saveIdComparison;
|
||||
int rankComparison = Rank.CompareTo(other.Rank);
|
||||
if (rankComparison != 0) return rankComparison;
|
||||
return Index.CompareTo(other.Index);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
return obj is SaveDataStruct other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(SaveDataStruct)}");
|
||||
}
|
||||
}
|
||||
}
|
40
src/LibHac/Fs/SaveIndexerStruct.cs
Normal file
40
src/LibHac/Fs/SaveIndexerStruct.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using LibHac.Kvdb;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SaveIndexerStruct : IExportable
|
||||
{
|
||||
public ulong SaveId { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public byte SpaceId { get; private set; }
|
||||
public byte Field19 { get; private set; }
|
||||
|
||||
public int ExportSize => 0x40;
|
||||
private bool _isFrozen;
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
if(output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
|
||||
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(output, SaveId);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(output.Slice(8), Size);
|
||||
output[0x18] = SpaceId;
|
||||
output[0x19] = Field19;
|
||||
}
|
||||
|
||||
public void FromBytes(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if(_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
|
||||
if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short.");
|
||||
|
||||
SaveId = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||
Size = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(8));
|
||||
SpaceId = input[0x18];
|
||||
Field19 = input[0x19];
|
||||
}
|
||||
|
||||
public void Freeze() => _isFrozen = true;
|
||||
}
|
||||
}
|
66
src/LibHac/Fs/UserId.cs
Normal file
66
src/LibHac/Fs/UserId.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public struct UserId : IEquatable<UserId>, IComparable<UserId>, IComparable
|
||||
{
|
||||
public readonly ulong High;
|
||||
public readonly ulong Low;
|
||||
|
||||
public UserId(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
public UserId(ReadOnlySpan<byte> uid)
|
||||
{
|
||||
ReadOnlySpan<ulong> longs = MemoryMarshal.Cast<byte, ulong>(uid);
|
||||
|
||||
High = longs[0];
|
||||
Low = longs[1];
|
||||
}
|
||||
|
||||
public bool Equals(UserId other)
|
||||
{
|
||||
return High == other.High && Low == other.Low;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UserId other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (High.GetHashCode() * 397) ^ Low.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(UserId other)
|
||||
{
|
||||
// ReSharper disable ImpureMethodCallOnReadonlyValueField
|
||||
int highComparison = High.CompareTo(other.High);
|
||||
if (highComparison != 0) return highComparison;
|
||||
return Low.CompareTo(other.Low);
|
||||
// ReSharper restore ImpureMethodCallOnReadonlyValueField
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return 1;
|
||||
return obj is UserId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(UserId)}");
|
||||
}
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
Span<ulong> longs = MemoryMarshal.Cast<byte, ulong>(output);
|
||||
|
||||
longs[0] = High;
|
||||
longs[1] = Low;
|
||||
}
|
||||
}
|
||||
}
|
31
src/LibHac/Kvdb/GenericValue.cs
Normal file
31
src/LibHac/Kvdb/GenericValue.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for handling any value used by <see cref="KeyValueDatabase{TKey,TValue}"/>
|
||||
/// </summary>
|
||||
public class GenericValue : IExportable
|
||||
{
|
||||
private bool _isFrozen;
|
||||
private byte[] _value;
|
||||
|
||||
public int ExportSize => _value?.Length ?? 0;
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
|
||||
|
||||
_value.CopyTo(output);
|
||||
}
|
||||
|
||||
public void FromBytes(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
|
||||
|
||||
_value = input.ToArray();
|
||||
}
|
||||
|
||||
public void Freeze() => _isFrozen = true;
|
||||
}
|
||||
}
|
16
src/LibHac/Kvdb/IExportable.cs
Normal file
16
src/LibHac/Kvdb/IExportable.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
public interface IExportable
|
||||
{
|
||||
int ExportSize { get; }
|
||||
void ToBytes(Span<byte> output);
|
||||
void FromBytes(ReadOnlySpan<byte> input);
|
||||
|
||||
/// <summary>
|
||||
/// Prevent further modification of this object.
|
||||
/// </summary>
|
||||
void Freeze();
|
||||
}
|
||||
}
|
24
src/LibHac/Kvdb/ImkvdbHeader.cs
Normal file
24
src/LibHac/Kvdb/ImkvdbHeader.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
|
||||
internal struct ImkvdbHeader
|
||||
{
|
||||
public const uint ExpectedMagic = 0x564B4D49; // IMKV
|
||||
|
||||
public uint Magic;
|
||||
public int Reserved;
|
||||
public int EntryCount;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xC)]
|
||||
internal struct ImkvdbEntryHeader
|
||||
{
|
||||
public const uint ExpectedMagic = 0x4E454D49; // IMEN
|
||||
|
||||
public uint Magic;
|
||||
public int KeySize;
|
||||
public int ValueSize;
|
||||
}
|
||||
}
|
79
src/LibHac/Kvdb/ImkvdbReader.cs
Normal file
79
src/LibHac/Kvdb/ImkvdbReader.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using static LibHac.Results;
|
||||
using static LibHac.Kvdb.ResultsKvdb;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
public ref struct ImkvdbReader
|
||||
{
|
||||
private ReadOnlySpan<byte> _data;
|
||||
private int _position;
|
||||
|
||||
public ImkvdbReader(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_data = data;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public Result ReadHeader(out int entryCount)
|
||||
{
|
||||
entryCount = default;
|
||||
|
||||
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdbInvalidKeyValue;
|
||||
|
||||
ref ImkvdbHeader header = ref Unsafe.As<byte, ImkvdbHeader>(ref Unsafe.AsRef(_data[_position]));
|
||||
|
||||
if (header.Magic != ImkvdbHeader.ExpectedMagic)
|
||||
{
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
entryCount = header.EntryCount;
|
||||
_position += Unsafe.SizeOf<ImkvdbHeader>();
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public Result GetEntrySize(out int keySize, out int valueSize)
|
||||
{
|
||||
keySize = default;
|
||||
valueSize = default;
|
||||
|
||||
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdbInvalidKeyValue;
|
||||
|
||||
ref ImkvdbEntryHeader header = ref Unsafe.As<byte, ImkvdbEntryHeader>(ref Unsafe.AsRef(_data[_position]));
|
||||
|
||||
if (header.Magic != ImkvdbEntryHeader.ExpectedMagic)
|
||||
{
|
||||
return ResultKvdbInvalidKeyValue;
|
||||
}
|
||||
|
||||
keySize = header.KeySize;
|
||||
valueSize = header.ValueSize;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public Result ReadEntry(out ReadOnlySpan<byte> key, out ReadOnlySpan<byte> value)
|
||||
{
|
||||
key = default;
|
||||
value = default;
|
||||
|
||||
Result sizeResult = GetEntrySize(out int keySize, out int valueSize);
|
||||
if (sizeResult.IsFailure()) return sizeResult;
|
||||
|
||||
_position += Unsafe.SizeOf<ImkvdbEntryHeader>();
|
||||
|
||||
if (_position + keySize + valueSize > _data.Length) return ResultKvdbInvalidKeyValue;
|
||||
|
||||
key = _data.Slice(_position, keySize);
|
||||
value = _data.Slice(_position + keySize, valueSize);
|
||||
|
||||
_position += keySize + valueSize;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
61
src/LibHac/Kvdb/ImkvdbWriter.cs
Normal file
61
src/LibHac/Kvdb/ImkvdbWriter.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
public ref struct ImkvdbWriter
|
||||
{
|
||||
private Span<byte> _data;
|
||||
private int _position;
|
||||
|
||||
public ImkvdbWriter(Span<byte> data)
|
||||
{
|
||||
_data = data;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public void WriteHeader(int entryCount)
|
||||
{
|
||||
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) throw new InvalidOperationException();
|
||||
|
||||
ref ImkvdbHeader header = ref Unsafe.As<byte, ImkvdbHeader>(ref _data[_position]);
|
||||
|
||||
header.Magic = ImkvdbHeader.ExpectedMagic;
|
||||
header.Reserved = 0;
|
||||
header.EntryCount = entryCount;
|
||||
|
||||
_position += Unsafe.SizeOf<ImkvdbHeader>();
|
||||
}
|
||||
|
||||
public void WriteEntry(IExportable key, IExportable value)
|
||||
{
|
||||
WriteEntryHeader(key.ExportSize, value.ExportSize);
|
||||
Write(key);
|
||||
Write(value);
|
||||
}
|
||||
|
||||
private void WriteEntryHeader(int keySize, int valueSize)
|
||||
{
|
||||
if (_position + Unsafe.SizeOf<ImkvdbEntryHeader>() > _data.Length) throw new InvalidOperationException();
|
||||
|
||||
ref ImkvdbEntryHeader header = ref Unsafe.As<byte, ImkvdbEntryHeader>(ref _data[_position]);
|
||||
|
||||
header.Magic = ImkvdbEntryHeader.ExpectedMagic;
|
||||
header.KeySize = keySize;
|
||||
header.ValueSize = valueSize;
|
||||
|
||||
_position += Unsafe.SizeOf<ImkvdbEntryHeader>();
|
||||
}
|
||||
|
||||
private void Write(IExportable value)
|
||||
{
|
||||
int valueSize = value.ExportSize;
|
||||
if (_position + valueSize > _data.Length) throw new InvalidOperationException();
|
||||
|
||||
Span<byte> dest = _data.Slice(_position, valueSize);
|
||||
value.ToBytes(dest);
|
||||
|
||||
_position += valueSize;
|
||||
}
|
||||
}
|
||||
}
|
93
src/LibHac/Kvdb/KeyValueDatabase.cs
Normal file
93
src/LibHac/Kvdb/KeyValueDatabase.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using static LibHac.Results;
|
||||
using static LibHac.Kvdb.ResultsKvdb;
|
||||
|
||||
namespace LibHac.Kvdb
|
||||
{
|
||||
// Todo: Save and load from file
|
||||
public class KeyValueDatabase<TKey, TValue>
|
||||
where TKey : IComparable<TKey>, IEquatable<TKey>, IExportable, new()
|
||||
where TValue : IExportable, new()
|
||||
{
|
||||
private Dictionary<TKey, TValue> KvDict { get; } = new Dictionary<TKey, TValue>();
|
||||
|
||||
public int Count => KvDict.Count;
|
||||
|
||||
public Result Get(TKey key, out TValue value)
|
||||
{
|
||||
if (!KvDict.TryGetValue(key, out value))
|
||||
{
|
||||
return ResultKvdbKeyNotFound;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public Result Set(TKey key, TValue value)
|
||||
{
|
||||
key.Freeze();
|
||||
|
||||
KvDict[key] = value;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var reader = new ImkvdbReader(data);
|
||||
|
||||
Result headerResult = reader.ReadHeader(out int entryCount);
|
||||
if (headerResult.IsFailure()) return headerResult;
|
||||
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
Result entryResult = reader.ReadEntry(out ReadOnlySpan<byte> keyBytes, out ReadOnlySpan<byte> valueBytes);
|
||||
if (entryResult.IsFailure()) return entryResult;
|
||||
|
||||
var key = new TKey();
|
||||
var value = new TValue();
|
||||
|
||||
key.FromBytes(keyBytes);
|
||||
value.FromBytes(valueBytes);
|
||||
|
||||
key.Freeze();
|
||||
|
||||
KvDict.Add(key, value);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public Result WriteDatabaseToBuffer(Span<byte> output)
|
||||
{
|
||||
var writer = new ImkvdbWriter(output);
|
||||
|
||||
writer.WriteHeader(KvDict.Count);
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> entry in KvDict.OrderBy(x => x.Key))
|
||||
{
|
||||
writer.WriteEntry(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
public int GetExportedSize()
|
||||
{
|
||||
int size = Unsafe.SizeOf<ImkvdbHeader>();
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> entry in KvDict)
|
||||
{
|
||||
size += Unsafe.SizeOf<ImkvdbEntryHeader>();
|
||||
size += entry.Key.ExportSize;
|
||||
size += entry.Value.ExportSize;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
13
src/LibHac/Kvdb/ResultsKvdb.cs
Normal file
13
src/LibHac/Kvdb/ResultsKvdb.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace LibHac.Kvdb
|
||||
{
|
||||
public static class ResultsKvdb
|
||||
{
|
||||
public const int ModuleKvdb = 20;
|
||||
|
||||
public static Result ResultKvdbTooLargeKey => new Result(ModuleKvdb, 1);
|
||||
public static Result ResultKvdbKeyNotFound => new Result(ModuleKvdb, 2);
|
||||
public static Result ResultKvdbAllocationFailed => new Result(ModuleKvdb, 4);
|
||||
public static Result ResultKvdbInvalidKeyValue => new Result(ModuleKvdb, 5);
|
||||
public static Result ResultKvdbBufferInsufficient => new Result(ModuleKvdb, 6);
|
||||
}
|
||||
}
|
84
src/LibHac/Ncm/ContentMetaKey.cs
Normal file
84
src/LibHac/Ncm/ContentMetaKey.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using LibHac.Kvdb;
|
||||
|
||||
namespace LibHac.Ncm
|
||||
{
|
||||
public class ContentMetaKey : IComparable<ContentMetaKey>, IComparable, IEquatable<ContentMetaKey>, IExportable
|
||||
{
|
||||
public ulong TitleId { get; private set; }
|
||||
public uint Version { get; private set; }
|
||||
public byte Type { get; private set; }
|
||||
public byte Flags { get; private set; }
|
||||
|
||||
public int ExportSize => 0x10;
|
||||
private bool _isFrozen;
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
|
||||
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(output, TitleId);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(8), Version);
|
||||
output[0xC] = Type;
|
||||
output[0xD] = Flags;
|
||||
}
|
||||
|
||||
public void FromBytes(ReadOnlySpan<byte> input)
|
||||
{
|
||||
if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
|
||||
if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short.");
|
||||
|
||||
TitleId = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||
Version = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(8));
|
||||
Type = input[0xC];
|
||||
Flags = input[0xD];
|
||||
}
|
||||
|
||||
public void Freeze() => _isFrozen = true;
|
||||
|
||||
public bool Equals(ContentMetaKey other)
|
||||
{
|
||||
return other != null && TitleId == other.TitleId && Version == other.Version &&
|
||||
Type == other.Type && Flags == other.Flags;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ContentMetaKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// ReSharper disable NonReadonlyMemberInGetHashCode
|
||||
int hashCode = TitleId.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ (int)Version;
|
||||
hashCode = (hashCode * 397) ^ Type.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Flags.GetHashCode();
|
||||
return hashCode;
|
||||
// ReSharper restore NonReadonlyMemberInGetHashCode
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ContentMetaKey other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
if (ReferenceEquals(null, other)) return 1;
|
||||
int titleIdComparison = TitleId.CompareTo(other.TitleId);
|
||||
if (titleIdComparison != 0) return titleIdComparison;
|
||||
int versionComparison = Version.CompareTo(other.Version);
|
||||
if (versionComparison != 0) return versionComparison;
|
||||
int typeComparison = Type.CompareTo(other.Type);
|
||||
if (typeComparison != 0) return typeComparison;
|
||||
return Flags.CompareTo(other.Flags);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
return obj is ContentMetaKey other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentMetaKey)}");
|
||||
}
|
||||
}
|
||||
}
|
28
src/LibHac/Result.cs
Normal file
28
src/LibHac/Result.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace LibHac
|
||||
{
|
||||
public struct Result
|
||||
{
|
||||
public readonly int Value;
|
||||
|
||||
public Result(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Result(int module, int description)
|
||||
{
|
||||
Value = (description << 9) | module;
|
||||
}
|
||||
|
||||
public int Description => (Value >> 9) & 0x1FFF;
|
||||
public int Module => Value & 0x1FF;
|
||||
|
||||
public bool IsSuccess() => Value == 0;
|
||||
public bool IsFailure() => Value != 0;
|
||||
}
|
||||
|
||||
public static class Results
|
||||
{
|
||||
public static Result ResultSuccess => new Result(0);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue