mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Begin implementing IFileSystem
This commit is contained in:
parent
ec4c603afc
commit
71c4e6746b
24 changed files with 905 additions and 16 deletions
|
@ -53,7 +53,7 @@ class Build : NukeBuild
|
||||||
.DependsOn(Clean)
|
.DependsOn(Clean)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
var settings = new DotNetRestoreSettings()
|
DotNetRestoreSettings settings = new DotNetRestoreSettings()
|
||||||
.SetProjectFile(Solution);
|
.SetProjectFile(Solution);
|
||||||
|
|
||||||
if (EnvironmentInfo.IsUnix) settings = settings.RemoveRuntimes("net46");
|
if (EnvironmentInfo.IsUnix) settings = settings.RemoveRuntimes("net46");
|
||||||
|
@ -65,7 +65,7 @@ class Build : NukeBuild
|
||||||
.DependsOn(Restore)
|
.DependsOn(Restore)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
var buildSettings = new DotNetBuildSettings()
|
DotNetBuildSettings buildSettings = new DotNetBuildSettings()
|
||||||
.SetProjectFile(Solution)
|
.SetProjectFile(Solution)
|
||||||
.EnableNoRestore()
|
.EnableNoRestore()
|
||||||
.SetConfiguration(Configuration);
|
.SetConfiguration(Configuration);
|
||||||
|
@ -74,7 +74,7 @@ class Build : NukeBuild
|
||||||
|
|
||||||
DotNetBuild(s => buildSettings);
|
DotNetBuild(s => buildSettings);
|
||||||
|
|
||||||
var publishSettings = new DotNetPublishSettings()
|
DotNetPublishSettings publishSettings = new DotNetPublishSettings()
|
||||||
.EnableNoRestore()
|
.EnableNoRestore()
|
||||||
.SetConfiguration(Configuration);
|
.SetConfiguration(Configuration);
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class Build : NukeBuild
|
||||||
.DependsOn(Compile)
|
.DependsOn(Compile)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
var settings = new DotNetPackSettings()
|
DotNetPackSettings settings = new DotNetPackSettings()
|
||||||
.SetProject(LibHacProject)
|
.SetProject(LibHacProject)
|
||||||
.EnableNoBuild()
|
.EnableNoBuild()
|
||||||
.SetConfiguration(Configuration)
|
.SetConfiguration(Configuration)
|
||||||
|
@ -154,7 +154,7 @@ class Build : NukeBuild
|
||||||
.DependsOn(Compile)
|
.DependsOn(Compile)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
var settings = new DotNetTestSettings()
|
DotNetTestSettings settings = new DotNetTestSettings()
|
||||||
.SetProjectFile(LibHacTestProject)
|
.SetProjectFile(LibHacTestProject)
|
||||||
.EnableNoBuild()
|
.EnableNoBuild()
|
||||||
.SetConfiguration(Configuration);
|
.SetConfiguration(Configuration);
|
||||||
|
|
|
@ -4,7 +4,7 @@ using DiscUtils.Fat;
|
||||||
|
|
||||||
namespace LibHac.Nand
|
namespace LibHac.Nand
|
||||||
{
|
{
|
||||||
public class NandPartition : IFileSystem
|
public class NandPartition : IFileSystemOld
|
||||||
{
|
{
|
||||||
public FatFileSystem Fs { get; }
|
public FatFileSystem Fs { get; }
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ using System.IO;
|
||||||
|
|
||||||
namespace LibHac
|
namespace LibHac
|
||||||
{
|
{
|
||||||
public class FileSystem : IFileSystem
|
public class FileSystem : IFileSystemOld
|
||||||
{
|
{
|
||||||
public string Root { get; }
|
public string Root { get; }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace LibHac
|
namespace LibHac
|
||||||
{
|
{
|
||||||
public interface IFileSystem
|
public interface IFileSystemOld
|
||||||
{
|
{
|
||||||
bool FileExists(string path);
|
bool FileExists(string path);
|
||||||
bool DirectoryExists(string path);
|
bool DirectoryExists(string path);
|
|
@ -136,7 +136,7 @@ namespace LibHac.IO
|
||||||
length = (int)Math.Min(Length - offset, length);
|
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.Length = length;
|
||||||
block.Index = index;
|
block.Index = index;
|
||||||
block.Dirty = false;
|
block.Dirty = false;
|
||||||
|
|
22
src/LibHac/IO/DirectoryEntry.cs
Normal file
22
src/LibHac/IO/DirectoryEntry.cs
Normal 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
23
src/LibHac/IO/FileBase.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
src/LibHac/IO/FileSystemExtensions.cs
Normal file
68
src/LibHac/IO/FileSystemExtensions.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/LibHac/IO/HierarchicalRomFileTable.cs
Normal file
7
src/LibHac/IO/HierarchicalRomFileTable.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace LibHac.IO
|
||||||
|
{
|
||||||
|
public class HierarchicalRomFileTable
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
10
src/LibHac/IO/IDirectory.cs
Normal file
10
src/LibHac/IO/IDirectory.cs
Normal 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
13
src/LibHac/IO/IFile.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
27
src/LibHac/IO/IFileSystem.cs
Normal file
27
src/LibHac/IO/IFileSystem.cs
Normal 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
102
src/LibHac/IO/PathTools.cs
Normal 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] == '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
src/LibHac/IO/RomFsDirectory.cs
Normal file
88
src/LibHac/IO/RomFsDirectory.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/LibHac/IO/RomFsFile.cs
Normal file
48
src/LibHac/IO/RomFsFile.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
src/LibHac/IO/RomFsFileSystem.cs
Normal file
153
src/LibHac/IO/RomFsFileSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,7 +95,7 @@ namespace LibHac.IO
|
||||||
|
|
||||||
if (Length != -1)
|
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 (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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
271
src/LibHac/IO/ValueStringBuilder.cs
Normal file
271
src/LibHac/IO/ValueStringBuilder.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
long start = file.Position;
|
long start = file.Position;
|
||||||
|
|
||||||
BinaryReader reader = new BinaryReader(file);
|
var reader = new BinaryReader(file);
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++)
|
for (int i = 0; i < 16; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -255,6 +255,15 @@ namespace LibHac
|
||||||
return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen);
|
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>
|
/// <summary>
|
||||||
/// Sets a base <see cref="Nca"/> to use when reading patches.
|
/// Sets a base <see cref="Nca"/> to use when reading patches.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -31,6 +31,9 @@ namespace LibHac
|
||||||
dir = dir.ParentDir;
|
dir = dir.ParentDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo
|
||||||
|
if (list.Count == 1) list.Add("/");
|
||||||
|
|
||||||
for (int i = list.Count - 1; i >= 0; i--)
|
for (int i = list.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
sb.Append(list[i]);
|
sb.Append(list[i]);
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace LibHac
|
||||||
public class SwitchFs : IDisposable
|
public class SwitchFs : IDisposable
|
||||||
{
|
{
|
||||||
public Keyset Keyset { get; }
|
public Keyset Keyset { get; }
|
||||||
public IFileSystem Fs { get; }
|
public IFileSystemOld Fs { get; }
|
||||||
public string ContentsDir { get; }
|
public string ContentsDir { get; }
|
||||||
public string SaveDir { get; }
|
public string SaveDir { get; }
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace LibHac
|
||||||
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
|
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
|
||||||
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
|
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
|
||||||
|
|
||||||
public SwitchFs(Keyset keyset, IFileSystem fs)
|
public SwitchFs(Keyset keyset, IFileSystemOld fs)
|
||||||
{
|
{
|
||||||
Fs = fs;
|
Fs = fs;
|
||||||
Keyset = keyset;
|
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 files = new List<string>();
|
||||||
var storages = new List<IStorage>();
|
var storages = new List<IStorage>();
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
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());
|
ctx.Logger.LogMessage(pfs.Print());
|
||||||
|
|
||||||
if (ctx.Options.OutDir != null)
|
if (ctx.Options.OutDir != null)
|
||||||
|
|
45
tests/LibHac.Tests/PathToolsTests.cs
Normal file
45
tests/LibHac.Tests/PathToolsTests.cs
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue