Begin implementing IFileSystem

This commit is contained in:
Alex Barney 2018-12-23 14:49:28 -07:00
parent ec4c603afc
commit 71c4e6746b
24 changed files with 905 additions and 16 deletions

View file

@ -53,7 +53,7 @@ class Build : NukeBuild
.DependsOn(Clean)
.Executes(() =>
{
var settings = new DotNetRestoreSettings()
DotNetRestoreSettings settings = new DotNetRestoreSettings()
.SetProjectFile(Solution);
if (EnvironmentInfo.IsUnix) settings = settings.RemoveRuntimes("net46");
@ -65,7 +65,7 @@ class Build : NukeBuild
.DependsOn(Restore)
.Executes(() =>
{
var buildSettings = new DotNetBuildSettings()
DotNetBuildSettings buildSettings = new DotNetBuildSettings()
.SetProjectFile(Solution)
.EnableNoRestore()
.SetConfiguration(Configuration);
@ -74,7 +74,7 @@ class Build : NukeBuild
DotNetBuild(s => buildSettings);
var publishSettings = new DotNetPublishSettings()
DotNetPublishSettings publishSettings = new DotNetPublishSettings()
.EnableNoRestore()
.SetConfiguration(Configuration);
@ -105,7 +105,7 @@ class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
var settings = new DotNetPackSettings()
DotNetPackSettings settings = new DotNetPackSettings()
.SetProject(LibHacProject)
.EnableNoBuild()
.SetConfiguration(Configuration)
@ -154,7 +154,7 @@ class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
var settings = new DotNetTestSettings()
DotNetTestSettings settings = new DotNetTestSettings()
.SetProjectFile(LibHacTestProject)
.EnableNoBuild()
.SetConfiguration(Configuration);

View file

@ -4,7 +4,7 @@ using DiscUtils.Fat;
namespace LibHac.Nand
{
public class NandPartition : IFileSystem
public class NandPartition : IFileSystemOld
{
public FatFileSystem Fs { get; }

View file

@ -4,7 +4,7 @@ using System.IO;
namespace LibHac
{
public class FileSystem : IFileSystem
public class FileSystem : IFileSystemOld
{
public string Root { get; }

View file

@ -2,7 +2,7 @@
namespace LibHac
{
public interface IFileSystem
public interface IFileSystemOld
{
bool FileExists(string path);
bool DirectoryExists(string path);

View file

@ -136,7 +136,7 @@ namespace LibHac.IO
length = (int)Math.Min(Length - offset, length);
}
BaseStorage.Read(block.Buffer, offset, length, 0);
BaseStorage.Read(block.Buffer.AsSpan(0, length), offset);
block.Length = length;
block.Index = index;
block.Dirty = false;

View file

@ -0,0 +1,22 @@
namespace LibHac.IO
{
public class DirectoryEntry
{
public string Name { get; }
public DirectoryEntryType Type { get; }
public long Size { get; }
public DirectoryEntry(string name, DirectoryEntryType type, long size)
{
Name = name;
Type = type;
Size = size;
}
}
public enum DirectoryEntryType
{
Directory,
File
}
}

23
src/LibHac/IO/FileBase.cs Normal file
View file

@ -0,0 +1,23 @@
using System;
namespace LibHac.IO
{
public abstract class FileBase : IFile
{
public abstract int Read(Span<byte> destination, long offset);
public abstract void Write(ReadOnlySpan<byte> source, long offset);
public abstract void Flush();
public abstract long GetSize();
public abstract long SetSize();
protected int GetAvailableSizeAndValidate(ReadOnlySpan<byte> span, long offset)
{
long fileLength = GetSize();
if (span == null) throw new ArgumentNullException(nameof(span));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative.");
return (int)Math.Min(fileLength - offset, span.Length);
}
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace LibHac.IO
{
public static class FileSystemExtensions
{
public static void Extract(this IFileSystem fs, string outDir)
{
IDirectory root = fs.OpenDirectory("/", OpenDirectoryMode.All);
foreach (string filename in root.EnumerateFiles())
{
//Console.WriteLine(filename);
IFile file = fs.OpenFile(filename);
string outPath = Path.Combine(outDir, filename.TrimStart('/'));
string directoryName = Path.GetDirectoryName(outPath);
if(!string.IsNullOrWhiteSpace(directoryName)) Directory.CreateDirectory(directoryName);
using (var outFile = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite))
{
file.CopyTo(outFile);
}
}
}
public static IEnumerable<string> EnumerateFiles(this IDirectory directory)
{
DirectoryEntry[] entries = directory.Read();
foreach (DirectoryEntry entry in entries)
{
if (entry.Type == DirectoryEntryType.Directory)
{
foreach (string a in EnumerateFiles(directory.ParentFileSystem.OpenDirectory(entry.Name, OpenDirectoryMode.All)))
{
yield return a;
}
}
if (entry.Type == DirectoryEntryType.File)
{
yield return entry.Name;
}
}
}
public static void CopyTo(this IFile file, Stream output)
{
const int bufferSize = 0x8000;
long remaining = file.GetSize();
long inOffset = 0;
var buffer = new byte[bufferSize];
while (remaining > 0)
{
int toWrite = (int)Math.Min(buffer.Length, remaining);
file.Read(buffer.AsSpan(0, toWrite), inOffset);
output.Write(buffer, 0, toWrite);
remaining -= toWrite;
inOffset += toWrite;
}
}
}
}

View file

@ -0,0 +1,7 @@
namespace LibHac.IO
{
public class HierarchicalRomFileTable
{
}
}

View file

@ -0,0 +1,10 @@
namespace LibHac.IO
{
public interface IDirectory
{
IFileSystem ParentFileSystem { get; }
DirectoryEntry[] Read();
int GetEntryCount();
}
}

13
src/LibHac/IO/IFile.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
namespace LibHac.IO
{
public interface IFile
{
int Read(Span<byte> destination, long offset);
void Write(ReadOnlySpan<byte> source, long offset);
void Flush();
long GetSize();
long SetSize();
}
}

View file

@ -0,0 +1,27 @@
using System;
namespace LibHac.IO
{
public interface IFileSystem
{
void Commit();
void CreateDirectory(string path);
void CreateFile(string path, long size);
void DeleteDirectory(string path);
void DeleteFile(string path);
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
IFile OpenFile(string path);
void RenameDirectory(string srcPath, string dstPath);
void RenameFile(string srcPath, string dstPath);
bool DirectoryExists(string path);
bool FileExists(string path);
}
[Flags]
public enum OpenDirectoryMode
{
Directories = 1,
Files = 2,
All = Directories | Files
}
}

102
src/LibHac/IO/PathTools.cs Normal file
View file

@ -0,0 +1,102 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace LibHac.IO
{
public static class PathTools
{
public static readonly char DirectorySeparator = '/';
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
public static string Normalize(string inPath)
{
ReadOnlySpan<char> path = inPath.AsSpan();
if (path.Length == 0) return DirectorySeparator.ToString();
if (path[0] != DirectorySeparator)
{
throw new InvalidDataException($"{nameof(path)} must begin with '{DirectorySeparator}'");
}
Span<char> initialBuffer = stackalloc char[0x200];
var sb = new ValueStringBuilder(initialBuffer);
for (int i = 0; i < path.Length; i++)
{
char c = path[i];
if (IsDirectorySeparator(c) && i + 1 < path.Length)
{
// Skip this character if it's a directory separator and if the next character is, too,
// e.g. "parent//child" => "parent/child"
if (IsDirectorySeparator(path[i + 1])) continue;
// Skip this character and the next if it's referring to the current directory,
// e.g. "parent/./child" => "parent/child"
if (IsCurrentDirectory(path, i))
{
i++;
continue;
}
// Skip this character and the next two if it's referring to the parent directory,
// e.g. "parent/child/../grandchild" => "parent/grandchild"
if (IsParentDirectory(path, i))
{
// Unwind back to the last slash (and if there isn't one, clear out everything).
for (int s = sb.Length - 1; s >= 0; s--)
{
if (IsDirectorySeparator(sb[s]))
{
sb.Length = s;
break;
}
}
i += 2;
continue;
}
}
sb.Append(c);
}
// If we haven't changed the source path, return the original
if (sb.Length == inPath.Length)
{
return inPath;
}
if (sb.Length == 0)
{
sb.Append(DirectorySeparator);
}
return sb.ToString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c)
{
return c == DirectorySeparator;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsCurrentDirectory(ReadOnlySpan<char> path, int index)
{
return (index + 2 == path.Length || IsDirectorySeparator(path[index + 2])) &&
path[index + 1] == '.';
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsParentDirectory(ReadOnlySpan<char> path, int index)
{
return index + 2 < path.Length &&
(index + 3 == path.Length || IsDirectorySeparator(path[index + 3])) &&
path[index + 1] == '.' && path[index + 2] == '.';
}
}
}

View file

@ -0,0 +1,88 @@
using System.IO;
namespace LibHac.IO
{
public class RomFsDirectory : IDirectory
{
public IFileSystem ParentFileSystem { get; }
private RomfsDir Directory { get; }
private OpenDirectoryMode Mode { get; }
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
{
if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir))
{
throw new DirectoryNotFoundException(path);
}
ParentFileSystem = fs;
Directory = dir;
Mode = mode;
}
public DirectoryEntry[] Read()
{
int count = GetEntryCount();
var entries = new DirectoryEntry[count];
int index = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
RomfsDir dirEntry = Directory.FirstChild;
while (dirEntry != null)
{
entries[index] = new DirectoryEntry(dirEntry.FullPath, DirectoryEntryType.Directory, 0);
dirEntry = dirEntry.NextSibling;
index++;
}
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
RomfsFile fileEntry = Directory.FirstFile;
while (fileEntry != null)
{
entries[index] =
new DirectoryEntry(fileEntry.FullPath, DirectoryEntryType.File, fileEntry.DataLength);
fileEntry = fileEntry.NextSibling;
index++;
}
}
return entries;
}
public int GetEntryCount()
{
int count = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
RomfsDir dirEntry = Directory.FirstChild;
while (dirEntry != null)
{
count++;
dirEntry = dirEntry.NextSibling;
}
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
RomfsFile fileEntry = Directory.FirstFile;
while (fileEntry != null)
{
count++;
fileEntry = fileEntry.NextSibling;
}
}
return count;
}
}
}

View file

@ -0,0 +1,48 @@
using System;
namespace LibHac.IO
{
public class RomFsFile : FileBase
{
private IStorage BaseStorage { get; }
private long Offset { get; }
private long Size { get; }
public RomFsFile(IStorage baseStorage, long offset, long size)
{
BaseStorage = baseStorage;
Offset = offset;
Size = size;
}
public override int Read(Span<byte> destination, long offset)
{
long storageOffset = Offset + offset;
int toRead = GetAvailableSizeAndValidate(destination, offset);
BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
return toRead;
}
public override void Write(ReadOnlySpan<byte> source, long offset)
{
throw new NotImplementedException();
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long GetSize()
{
return Size;
}
public override long SetSize()
{
throw new NotSupportedException();
}
}
}

View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace LibHac.IO
{
public class RomFsFileSystem : IFileSystem
{
public RomfsHeader Header { get; }
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
public RomfsDir RootDir { get; }
public Dictionary<string, RomfsFile> FileDict { get; }
public Dictionary<string, RomfsDir> DirectoryDict { get; }
private IStorage BaseStorage { get; }
// todo Don't parse entire table when opening
public RomFsFileSystem(IStorage storage)
{
BaseStorage = storage;
byte[] dirMetaTable;
byte[] fileMetaTable;
using (var reader = new BinaryReader(BaseStorage.AsStream(), Encoding.Default, true))
{
Header = new RomfsHeader(reader);
reader.BaseStream.Position = Header.DirMetaTableOffset;
dirMetaTable = reader.ReadBytes((int)Header.DirMetaTableSize);
reader.BaseStream.Position = Header.FileMetaTableOffset;
fileMetaTable = reader.ReadBytes((int)Header.FileMetaTableSize);
}
using (var reader = new BinaryReader(new MemoryStream(dirMetaTable)))
{
int position = 0;
while (position + 20 < Header.DirMetaTableSize)
{
var dir = new RomfsDir(reader) { Offset = position };
Directories.Add(dir);
if (dir.ParentDirOffset == position) RootDir = dir;
position = (int)reader.BaseStream.Position;
}
}
using (var reader = new BinaryReader(new MemoryStream(fileMetaTable)))
{
int position = 0;
while (position + 20 < Header.FileMetaTableSize)
{
var file = new RomfsFile(reader) { Offset = position };
Files.Add(file);
position = (int)reader.BaseStream.Position;
}
}
SetReferences();
RomfsEntry.ResolveFilenames(Files);
RomfsEntry.ResolveFilenames(Directories);
FileDict = Files.ToDictionary(x => x.FullPath, x => x);
DirectoryDict = Directories.ToDictionary(x => x.FullPath, x => x);
}
private void SetReferences()
{
Dictionary<int, RomfsDir> dirDict = Directories.ToDictionary(x => x.Offset, x => x);
Dictionary<int, RomfsFile> fileDict = Files.ToDictionary(x => x.Offset, x => x);
foreach (RomfsDir dir in Directories)
{
if (dir.ParentDirOffset >= 0 && dir.ParentDirOffset != dir.Offset) dir.ParentDir = dirDict[dir.ParentDirOffset];
if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset];
if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset];
if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset];
if (dir.NextDirHashOffset >= 0) dir.NextDirHash = dirDict[dir.NextDirHashOffset];
}
foreach (RomfsFile file in Files)
{
if (file.ParentDirOffset >= 0) file.ParentDir = dirDict[file.ParentDirOffset];
if (file.NextSiblingOffset >= 0) file.NextSibling = fileDict[file.NextSiblingOffset];
if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset];
}
}
public void Commit()
{
throw new NotSupportedException();
}
public void CreateDirectory(string path)
{
throw new NotSupportedException();
}
public void CreateFile(string path, long size)
{
throw new NotSupportedException();
}
public void DeleteDirectory(string path)
{
throw new NotSupportedException();
}
public void DeleteFile(string path)
{
throw new NotSupportedException();
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
{
return new RomFsDirectory(this, path, mode);
}
public IFile OpenFile(string path)
{
if (!FileDict.TryGetValue(path, out RomfsFile file))
{
throw new FileNotFoundException();
}
return OpenFile(file);
}
public IFile OpenFile(RomfsFile file)
{
return new RomFsFile(BaseStorage, Header.DataOffset + file.DataOffset, file.DataLength);
}
public void RenameDirectory(string srcPath, string dstPath)
{
throw new NotSupportedException();
}
public void RenameFile(string srcPath, string dstPath)
{
throw new NotSupportedException();
}
public bool DirectoryExists(string path)
{
return DirectoryDict.ContainsKey(path);
}
public bool FileExists(string path)
{
return FileDict.ContainsKey(path);
}
}
}

View file

@ -95,7 +95,7 @@ namespace LibHac.IO
if (Length != -1)
{
if (offset + count > Length) throw new ArgumentException();
if (offset + count > Length) throw new ArgumentException("The given offset and count exceed the length of the Storage");
}
}
@ -107,7 +107,7 @@ namespace LibHac.IO
if (Length != -1)
{
if (offset + destination.Length > Length) throw new ArgumentException("Storage");
if (offset + destination.Length > Length) throw new ArgumentException("The given offset and count exceed the length of the Storage");
}
}
}

View file

@ -0,0 +1,271 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.IO
{
internal ref struct ValueStringBuilder
{
private char[] _arrayToReturnToPool;
private Span<char> _chars;
private int _pos;
public ValueStringBuilder(Span<char> initialBuffer)
{
_arrayToReturnToPool = null;
_chars = initialBuffer;
_pos = 0;
}
public ValueStringBuilder(int initialCapacity)
{
_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
_chars = _arrayToReturnToPool;
_pos = 0;
}
public int Length
{
get => _pos;
set
{
Debug.Assert(value >= 0);
Debug.Assert(value <= _chars.Length);
_pos = value;
}
}
public int Capacity => _chars.Length;
public void EnsureCapacity(int capacity)
{
if (capacity > _chars.Length)
Grow(capacity - _chars.Length);
}
/// <summary>
/// Get a pinnable reference to the builder.
/// Does not ensure there is a null char after <see cref="Length"/>
/// This overload is pattern matched in the C# 7.3+ compiler so you can omit
/// the explicit method call, and write eg "fixed (char* c = builder)"
/// </summary>
public ref char GetPinnableReference()
{
return ref MemoryMarshal.GetReference(_chars);
}
/// <summary>
/// Get a pinnable reference to the builder.
/// </summary>
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
public ref char GetPinnableReference(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return ref MemoryMarshal.GetReference(_chars);
}
public ref char this[int index]
{
get
{
Debug.Assert(index < _pos);
return ref _chars[index];
}
}
public override string ToString()
{
string s = _chars.Slice(0, _pos).ToString();
Dispose();
return s;
}
/// <summary>Returns the underlying storage of the builder.</summary>
public Span<char> RawChars => _chars;
/// <summary>
/// Returns a span around the contents of the builder.
/// </summary>
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
public ReadOnlySpan<char> AsSpan(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return _chars.Slice(0, _pos);
}
public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos);
public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start);
public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
public bool TryCopyTo(Span<char> destination, out int charsWritten)
{
if (_chars.Slice(0, _pos).TryCopyTo(destination))
{
charsWritten = _pos;
Dispose();
return true;
}
else
{
charsWritten = 0;
Dispose();
return false;
}
}
public void Insert(int index, char value, int count)
{
if (_pos > _chars.Length - count)
{
Grow(count);
}
int remaining = _pos - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
_chars.Slice(index, count).Fill(value);
_pos += count;
}
public void Insert(int index, string s)
{
int count = s.Length;
if (_pos > (_chars.Length - count))
{
Grow(count);
}
int remaining = _pos - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
s.AsSpan().CopyTo(_chars.Slice(index));
_pos += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(char c)
{
int pos = _pos;
if ((uint)pos < (uint)_chars.Length)
{
_chars[pos] = c;
_pos = pos + 1;
}
else
{
GrowAndAppend(c);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string s)
{
int pos = _pos;
if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
{
_chars[pos] = s[0];
_pos = pos + 1;
}
else
{
AppendSlow(s);
}
}
private void AppendSlow(string s)
{
int pos = _pos;
if (pos > _chars.Length - s.Length)
{
Grow(s.Length);
}
s.AsSpan().CopyTo(_chars.Slice(pos));
_pos += s.Length;
}
public void Append(char c, int count)
{
if (_pos > _chars.Length - count)
{
Grow(count);
}
Span<char> dst = _chars.Slice(_pos, count);
for (int i = 0; i < dst.Length; i++)
{
dst[i] = c;
}
_pos += count;
}
public void Append(ReadOnlySpan<char> value)
{
int pos = _pos;
if (pos > _chars.Length - value.Length)
{
Grow(value.Length);
}
value.CopyTo(_chars.Slice(_pos));
_pos += value.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<char> AppendSpan(int length)
{
int origPos = _pos;
if (origPos > _chars.Length - length)
{
Grow(length);
}
_pos = origPos + length;
return _chars.Slice(origPos, length);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowAndAppend(char c)
{
Grow(1);
Append(c);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int requiredAdditionalCapacity)
{
Debug.Assert(requiredAdditionalCapacity > 0);
char[] poolArray = ArrayPool<char>.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2));
_chars.CopyTo(poolArray);
char[] toReturn = _arrayToReturnToPool;
_chars = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
ArrayPool<char>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
char[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
ArrayPool<char>.Shared.Return(toReturn);
}
}
}
}

View file

@ -64,7 +64,7 @@ namespace LibHac
{
long start = file.Position;
BinaryReader reader = new BinaryReader(file);
var reader = new BinaryReader(file);
for (int i = 0; i < 16; i++)
{

View file

@ -255,6 +255,15 @@ namespace LibHac
return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen);
}
public IFileSystem OpenSectionFileSystem(int index)
{
IStorage storage = OpenSection(index, false, IntegrityCheckLevel.ErrorOnInvalid, true);
var fs = new RomFsFileSystem(storage);
return fs;
}
/// <summary>
/// Sets a base <see cref="Nca"/> to use when reading patches.
/// </summary>

View file

@ -31,6 +31,9 @@ namespace LibHac
dir = dir.ParentDir;
}
//todo
if (list.Count == 1) list.Add("/");
for (int i = list.Count - 1; i >= 0; i--)
{
sb.Append(list[i]);

View file

@ -12,7 +12,7 @@ namespace LibHac
public class SwitchFs : IDisposable
{
public Keyset Keyset { get; }
public IFileSystem Fs { get; }
public IFileSystemOld Fs { get; }
public string ContentsDir { get; }
public string SaveDir { get; }
@ -21,7 +21,7 @@ namespace LibHac
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
public SwitchFs(Keyset keyset, IFileSystem fs)
public SwitchFs(Keyset keyset, IFileSystemOld fs)
{
Fs = fs;
Keyset = keyset;
@ -232,7 +232,7 @@ namespace LibHac
}
}
internal static IStorage OpenSplitNcaStorage(IFileSystem fs, string path)
internal static IStorage OpenSplitNcaStorage(IFileSystemOld fs, string path)
{
var files = new List<string>();
var storages = new List<IStorage>();

View file

@ -13,7 +13,7 @@ namespace hactoolnet
{
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
{
Pfs pfs = new Pfs(file.AsStorage());
var pfs = new Pfs(file.AsStorage());
ctx.Logger.LogMessage(pfs.Print());
if (ctx.Options.OutDir != null)

View file

@ -0,0 +1,45 @@
using System.IO;
using LibHac.IO;
using Xunit;
namespace LibHac.Tests
{
public class PathToolsTests
{
public static object[][] NormalizedPathTestItems =
{
new object[] {"", "/"},
new object[] {"/", "/"},
new object[] {"/.", "/"},
new object[] {"/a/b/c", "/a/b/c"},
new object[] {"/a/b/../c", "/a/c"},
new object[] {"/a/b/c/..", "/a/b"},
new object[] {"/a/b/c/.", "/a/b/c"},
new object[] {"/a/../../..", "/"},
new object[] {"/a/../../../a/b/c", "/a/b/c"},
new object[] {"//a/b//.//c", "/a/b/c"},
new object[] {"/a/b/c/", "/a/b/c/"},
new object[] {"/a/./b/../c/", "/a/c/"},
new object[] {"/a/../../../", "/"},
new object[] {"//a/b//.//c/", "/a/b/c/"},
new object[] {@"/tmp/../", @"/"},
};
[Theory]
[MemberData(nameof(NormalizedPathTestItems))]
public static void NormalizePath(string path, string expected)
{
string actual = PathTools.Normalize(path);
Assert.Equal(expected, actual);
}
[Fact]
public static void NormalizeThrowsOnInvalidStartChar()
{
Assert.Throws<InvalidDataException>(() => PathTools.Normalize(@"c:\a\b\c"));
}
}
}