diff --git a/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs b/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs index 3d3bb82a..e7d22f9f 100644 --- a/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs +++ b/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs @@ -16,7 +16,11 @@ namespace LibHac.IO.Save public bool TryOpenFile(string path, out SaveFileInfo fileInfo) { - FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key); + if (!FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key)) + { + fileInfo = default; + return false; + } if (FileTable.TryGetValue(ref key, out FileSaveEntry value)) { @@ -83,9 +87,77 @@ namespace LibHac.IO.Save return true; } + public void AddFile(string path, ref SaveFileInfo fileInfo) + { + path = PathTools.Normalize(path); + ReadOnlySpan pathBytes = Util.GetUtf8Bytes(path); + + if (path == "/") throw new ArgumentException("Path cannot be empty"); + + CreateFileRecursiveInternal(pathBytes, ref fileInfo); + } + + private void CreateFileRecursiveInternal(ReadOnlySpan path, ref SaveFileInfo fileInfo) + { + var parser = new PathParser(path); + var key = new SaveEntryKey(parser.GetCurrent(), 0); + + int prevIndex = 0; + + while (!parser.IsFinished()) + { + int index = DirectoryTable.GetIndexFromKey(ref key).Index; + + if (index < 0) + { + var newEntry = new DirectorySaveEntry(); + index = DirectoryTable.Add(ref key, ref newEntry); + + if (prevIndex > 0) + { + DirectoryTable.GetValue(prevIndex, out DirectorySaveEntry parentEntry); + + newEntry.NextSibling = parentEntry.Pos.NextDirectory; + parentEntry.Pos.NextDirectory = index; + + DirectoryTable.SetValue(prevIndex, ref parentEntry); + DirectoryTable.SetValue(index, ref newEntry); + } + } + + prevIndex = index; + key.Parent = index; + parser.TryGetNext(out key.Name); + } + + { + int index = FileTable.GetIndexFromKey(ref key).Index; + var fileEntry = new FileSaveEntry(); + + if (index < 0) + { + index = FileTable.Add(ref key, ref fileEntry); + + DirectoryTable.GetValue(prevIndex, out DirectorySaveEntry parentEntry); + + fileEntry.NextSibling = (int)parentEntry.Pos.NextFile; + parentEntry.Pos.NextFile = index; + + DirectoryTable.SetValue(prevIndex, ref parentEntry); + } + + fileEntry.Info = fileInfo; + FileTable.SetValue(index, ref fileEntry); + } + } + public bool TryOpenDirectory(string path, out SaveFindPosition position) { - FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key); + if (!FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key)) + { + position = default; + return false; + } if (DirectoryTable.TryGetValue(ref key, out DirectorySaveEntry value)) { @@ -97,16 +169,21 @@ namespace LibHac.IO.Save return false; } - private void FindPathRecursive(ReadOnlySpan path, out SaveEntryKey key) + private bool FindPathRecursive(ReadOnlySpan path, out SaveEntryKey key) { var parser = new PathParser(path); key = new SaveEntryKey(parser.GetCurrent(), 0); while (!parser.IsFinished()) { - key.Parent = DirectoryTable.GetOffsetFromKey(ref key); + key.Parent = DirectoryTable.GetIndexFromKey(ref key).Index; + + if (key.Parent < 0) return false; + parser.TryGetNext(out key.Name); } + + return true; } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/src/LibHac/IO/Save/SaveFsList.cs b/src/LibHac/IO/Save/SaveFsList.cs index ad7af15a..21c57d4d 100644 --- a/src/LibHac/IO/Save/SaveFsList.cs +++ b/src/LibHac/IO/Save/SaveFsList.cs @@ -20,7 +20,7 @@ namespace LibHac.IO.Save Storage = tableStorage; } - public int GetOffsetFromKey(ref SaveEntryKey key) + public (int Index, int PreviousIndex) GetIndexFromKey(ref SaveEntryKey key) { Span entryBytes = stackalloc byte[_sizeOfEntry]; Span name = entryBytes.Slice(4, MaxNameLength); @@ -29,26 +29,92 @@ namespace LibHac.IO.Save int capacity = GetListCapacity(); ReadEntry(UsedListHeadIndex, entryBytes); + int prevIndex = UsedListHeadIndex; + int index = entry.Next; - while (entry.Next > 0) + while (index > 0) { - if (entry.Next > capacity) throw new IndexOutOfRangeException("Save entry index out of range"); + if (index > capacity) throw new IndexOutOfRangeException("Save entry index out of range"); - int entryId = entry.Next; - ReadEntry(entry.Next, out entry); + ReadEntry(index, out entry); if (entry.Parent == key.Parent && Util.StringSpansEqual(name, key.Name)) { - return entryId; + return (index, prevIndex); } + + prevIndex = index; + index = entry.Next; } - return -1; + return (-1, -1); + } + + public int Add(ref SaveEntryKey key, ref T value) + { + int index = GetIndexFromKey(ref key).Index; + + if (index != -1) + { + SetValue(index, ref value); + return index; + } + + index = AllocateEntry(); + + ReadEntry(index, out SaveFsEntry entry); + entry.Value = value; + WriteEntry(index, ref entry, ref key); + + return index; + } + + private int AllocateEntry() + { + ReadEntry(FreeListHeadIndex, out SaveFsEntry freeListHead); + ReadEntry(UsedListHeadIndex, out SaveFsEntry usedListHead); + + if (freeListHead.Next != 0) + { + ReadEntry(freeListHead.Next, out SaveFsEntry firstFreeEntry); + + int allocatedIndex = freeListHead.Next; + + freeListHead.Next = firstFreeEntry.Next; + firstFreeEntry.Next = usedListHead.Next; + usedListHead.Next = allocatedIndex; + + WriteEntry(FreeListHeadIndex, ref freeListHead); + WriteEntry(UsedListHeadIndex, ref usedListHead); + WriteEntry(allocatedIndex, ref firstFreeEntry); + + return allocatedIndex; + } + + int length = GetListLength(); + int capacity = GetListCapacity(); + + if (capacity == 0 || length >= capacity) + { + throw new NotImplementedException(); + } + + SetListLength(length + 1); + + ReadEntry(length, out SaveFsEntry newEntry); + + newEntry.Next = usedListHead.Next; + usedListHead.Next = length; + + WriteEntry(UsedListHeadIndex, ref usedListHead); + WriteEntry(length, ref newEntry); + + return length; } public bool TryGetValue(ref SaveEntryKey key, out T value) { - int index = GetOffsetFromKey(ref key); + int index = GetIndexFromKey(ref key).Index; if (index < 0) { @@ -123,6 +189,18 @@ namespace LibHac.IO.Save value = entry.Value; } + public void SetValue(int index, ref T value) + { + Span entryBytes = stackalloc byte[_sizeOfEntry]; + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(index, out entry); + + entry.Value = value; + + WriteEntry(index, ref entry); + } + private int GetListCapacity() { Span buf = stackalloc byte[sizeof(int)]; @@ -139,6 +217,22 @@ namespace LibHac.IO.Save return MemoryMarshal.Read(buf); } + private void SetListCapacity(int capacity) + { + Span buf = stackalloc byte[sizeof(int)]; + MemoryMarshal.Write(buf, ref capacity); + + Storage.Write(buf, 4); + } + + private void SetListLength(int length) + { + Span buf = stackalloc byte[sizeof(int)]; + MemoryMarshal.Write(buf, ref length); + + Storage.Write(buf, 0); + } + private void ReadEntry(int index, out SaveFsEntry entry) { Span bytes = stackalloc byte[_sizeOfEntry]; @@ -147,6 +241,34 @@ namespace LibHac.IO.Save entry = GetEntryFromBytes(bytes); } + private void WriteEntry(int index, ref SaveFsEntry entry, ref SaveEntryKey key) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + Span nameSpan = bytes.Slice(4, MaxNameLength); + + // Copy needed for .NET Framework compat + ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); + newEntry = entry; + + newEntry.Parent = key.Parent; + key.Name.CopyTo(nameSpan); + + nameSpan.Slice(key.Name.Length).Fill(0); + + WriteEntry(index, bytes); + } + + private void WriteEntry(int index, ref SaveFsEntry entry) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + + // Copy needed for .NET Framework compat + ref SaveFsEntry newEntry = ref GetEntryFromBytes(bytes); + newEntry = entry; + + WriteEntry(index, bytes); + } + private void ReadEntry(int index, Span entry) { Debug.Assert(entry.Length == _sizeOfEntry); @@ -155,6 +277,14 @@ namespace LibHac.IO.Save Storage.Read(entry, offset); } + private void WriteEntry(int index, Span entry) + { + Debug.Assert(entry.Length == _sizeOfEntry); + + int offset = index * _sizeOfEntry; + Storage.Write(entry, offset); + } + private ref SaveFsEntry GetEntryFromBytes(Span entry) { return ref MemoryMarshal.Cast(entry)[0];