mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Tons of WIP changes for paths
This commit is contained in:
parent
d6123a7fb2
commit
b37728eefd
@ -122,7 +122,7 @@ namespace Compression.BSA
|
||||
|
||||
public uint FileHash => _state.NameHash;
|
||||
public uint DirHash => _state.DirHash;
|
||||
public string FullName => _state.Path;
|
||||
public string FullName => (string)_state.Path;
|
||||
public int Index => _state.Index;
|
||||
|
||||
public void WriteHeader(BinaryWriter bw)
|
||||
@ -247,7 +247,7 @@ namespace Compression.BSA
|
||||
|
||||
public uint FileHash => _state.NameHash;
|
||||
public uint DirHash => _state.DirHash;
|
||||
public string FullName => _state.Path;
|
||||
public string FullName => (string)_state.Path;
|
||||
public int Index => _state.Index;
|
||||
|
||||
public void WriteHeader(BinaryWriter wtr)
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||
using Wabbajack.Common;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
|
||||
namespace Compression.BSA
|
||||
@ -23,7 +24,7 @@ namespace Compression.BSA
|
||||
|
||||
public class BA2Reader : IBSAReader
|
||||
{
|
||||
internal string _filename;
|
||||
internal AbsolutePath _filename;
|
||||
private Stream _stream;
|
||||
internal BinaryReader _rdr;
|
||||
internal uint _version;
|
||||
@ -35,7 +36,7 @@ namespace Compression.BSA
|
||||
|
||||
public bool HasNameTable => _nameTableOffset > 0;
|
||||
|
||||
public BA2Reader(string filename) : this(File.OpenRead(filename))
|
||||
public BA2Reader(AbsolutePath filename) : this(filename.OpenRead())
|
||||
{
|
||||
_filename = filename;
|
||||
}
|
||||
@ -174,7 +175,7 @@ namespace Compression.BSA
|
||||
|
||||
public string FullPath { get; set; }
|
||||
|
||||
public string Path => FullPath;
|
||||
public RelativePath Path => new RelativePath(FullPath);
|
||||
public uint Size => (uint)_chunks.Sum(f => f._fullSz) + HeaderSize + sizeof(uint);
|
||||
public FileStateObject State => new BA2DX10EntryState(this);
|
||||
|
||||
@ -186,7 +187,7 @@ namespace Compression.BSA
|
||||
|
||||
WriteHeader(bw);
|
||||
|
||||
using (var fs = File.OpenRead(_bsa._filename))
|
||||
using (var fs = _bsa._filename.OpenRead())
|
||||
using (var br = new BinaryReader(fs))
|
||||
{
|
||||
foreach (var chunk in _chunks)
|
||||
@ -328,7 +329,7 @@ namespace Compression.BSA
|
||||
public BA2DX10EntryState() { }
|
||||
public BA2DX10EntryState(BA2DX10Entry ba2Dx10Entry)
|
||||
{
|
||||
Path = ba2Dx10Entry.FullPath;
|
||||
Path = ba2Dx10Entry.Path;
|
||||
NameHash = ba2Dx10Entry._nameHash;
|
||||
Extension = ba2Dx10Entry._extension;
|
||||
DirHash = ba2Dx10Entry._dirHash;
|
||||
@ -438,13 +439,13 @@ namespace Compression.BSA
|
||||
|
||||
public string FullPath { get; set; }
|
||||
|
||||
public string Path => FullPath;
|
||||
public RelativePath Path => new RelativePath(FullPath);
|
||||
public uint Size => _realSize;
|
||||
public FileStateObject State => new BA2FileEntryState(this);
|
||||
|
||||
public void CopyDataTo(Stream output)
|
||||
{
|
||||
using (var fs = File.OpenRead(_bsa._filename))
|
||||
using (var fs = _bsa._filename.OpenRead())
|
||||
{
|
||||
fs.Seek((long) _offset, SeekOrigin.Begin);
|
||||
uint len = Compressed ? _size : _realSize;
|
||||
@ -479,7 +480,7 @@ namespace Compression.BSA
|
||||
Flags = ba2FileEntry._flags;
|
||||
Align = ba2FileEntry._align;
|
||||
Compressed = ba2FileEntry.Compressed;
|
||||
Path = ba2FileEntry.FullPath;
|
||||
Path = ba2FileEntry.Path;
|
||||
Extension = ba2FileEntry._extension;
|
||||
Index = ba2FileEntry._index;
|
||||
}
|
||||
|
@ -59,11 +59,11 @@ namespace Compression.BSA
|
||||
set => _version = (uint) value;
|
||||
}
|
||||
|
||||
public IEnumerable<string> FolderNames
|
||||
public IEnumerable<RelativePath> FolderNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return _files.Select(f => Path.GetDirectoryName(f.Path)).Distinct();
|
||||
return _files.Select(f => f.Path.Parent).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,13 +128,11 @@ namespace Compression.BSA
|
||||
|
||||
public void RegenFolderRecords()
|
||||
{
|
||||
_folders = _files.GroupBy(f => Path.GetDirectoryName(f.Path.ToLowerInvariant()))
|
||||
_folders = _files.GroupBy(f => f.Path.Parent)
|
||||
.Select(f => new FolderRecordBuilder(this, f.Key, f.ToList()))
|
||||
.OrderBy(f => f._hash)
|
||||
.ToList();
|
||||
|
||||
var lnk = _files.Where(f => f.Path.EndsWith(".lnk")).FirstOrDefault();
|
||||
|
||||
foreach (var folder in _folders)
|
||||
foreach (var file in folder._files)
|
||||
file._folder = folder;
|
||||
@ -156,13 +154,13 @@ namespace Compression.BSA
|
||||
internal ulong _offset;
|
||||
internal uint _recordSize;
|
||||
|
||||
public FolderRecordBuilder(BSABuilder bsa, string folderName, IEnumerable<FileEntry> files)
|
||||
public FolderRecordBuilder(BSABuilder bsa, RelativePath folderName, IEnumerable<FileEntry> files)
|
||||
{
|
||||
_files = files.OrderBy(f => f._hash);
|
||||
Name = folderName.ToLowerInvariant();
|
||||
Name = folderName;
|
||||
_bsa = bsa;
|
||||
// Folders don't have extensions, so let's make sure we cut it out
|
||||
_hash = Name.GetBSAHash("");
|
||||
_hash = Name.GetBSAHash();
|
||||
_fileCount = (uint) files.Count();
|
||||
_nameBytes = folderName.ToBZString(_bsa.HeaderType);
|
||||
_recordSize = sizeof(ulong) + sizeof(uint) + sizeof(uint);
|
||||
@ -170,7 +168,7 @@ namespace Compression.BSA
|
||||
|
||||
public ulong Hash => _hash;
|
||||
|
||||
public string Name { get; }
|
||||
public RelativePath Name { get; }
|
||||
|
||||
public ulong SelfSize
|
||||
{
|
||||
@ -232,17 +230,17 @@ namespace Compression.BSA
|
||||
internal byte[] _nameBytes;
|
||||
private long _offsetOffset;
|
||||
internal int _originalSize;
|
||||
internal string _path;
|
||||
internal RelativePath _path;
|
||||
private byte[] _pathBSBytes;
|
||||
internal byte[] _pathBytes;
|
||||
private Stream _srcData;
|
||||
|
||||
public static FileEntry Create(BSABuilder bsa, string path, Stream src, bool flipCompression)
|
||||
public static FileEntry Create(BSABuilder bsa, RelativePath path, Stream src, bool flipCompression)
|
||||
{
|
||||
var entry = new FileEntry();
|
||||
entry._bsa = bsa;
|
||||
entry._path = path.ToLowerInvariant();
|
||||
entry._name = System.IO.Path.GetFileName(entry._path);
|
||||
entry._path = path;
|
||||
entry._name = (string)entry._path.FileName;
|
||||
entry._hash = entry._name.GetBSAHash();
|
||||
entry._nameBytes = entry._name.ToTermString(bsa.HeaderType);
|
||||
entry._pathBytes = entry._path.ToTermString(bsa.HeaderType);
|
||||
@ -267,7 +265,7 @@ namespace Compression.BSA
|
||||
}
|
||||
}
|
||||
|
||||
public string Path => _path;
|
||||
public RelativePath Path => _path;
|
||||
|
||||
public bool FlipCompression => _flipCompression;
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Compression.BSA
|
||||
{
|
||||
public static class BSADispatch
|
||||
{
|
||||
public static IBSAReader OpenRead(string filename)
|
||||
public static IBSAReader OpenRead(AbsolutePath filename)
|
||||
{
|
||||
var fourcc = "";
|
||||
using (var file = File.OpenRead(filename))
|
||||
using (var file = filename.OpenRead())
|
||||
{
|
||||
fourcc = Encoding.ASCII.GetString(new BinaryReader(file).ReadBytes(4));
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||
using K4os.Compression.LZ4.Streams;
|
||||
using Wabbajack.Common;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
|
||||
namespace Compression.BSA
|
||||
@ -52,7 +53,7 @@ namespace Compression.BSA
|
||||
internal uint _archiveFlags;
|
||||
internal uint _fileCount;
|
||||
internal uint _fileFlags;
|
||||
internal string _fileName;
|
||||
internal AbsolutePath _fileName;
|
||||
internal uint _folderCount;
|
||||
internal uint _folderRecordOffset;
|
||||
private List<FolderRecord> _folders;
|
||||
@ -63,7 +64,7 @@ namespace Compression.BSA
|
||||
internal uint _totalFolderNameLength;
|
||||
internal uint _version;
|
||||
|
||||
public BSAReader(string filename) : this(File.OpenRead(filename))
|
||||
public BSAReader(AbsolutePath filename) : this(filename.OpenRead())
|
||||
{
|
||||
_fileName = filename;
|
||||
}
|
||||
@ -270,12 +271,11 @@ namespace Compression.BSA
|
||||
src.BaseStream.Position = old_pos;
|
||||
}
|
||||
|
||||
public string Path
|
||||
public RelativePath Path
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Folder.Name)) return _name;
|
||||
return Folder.Name + "\\" + _name;
|
||||
return string.IsNullOrEmpty(Folder.Name) ? new RelativePath(_name) : new RelativePath(Folder.Name + "\\" + _name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,7 +304,7 @@ namespace Compression.BSA
|
||||
|
||||
public void CopyDataTo(Stream output)
|
||||
{
|
||||
using (var in_file = File.OpenRead(_bsa._fileName))
|
||||
using (var in_file = _bsa._fileName.OpenRead())
|
||||
using (var rdr = new BinaryReader(in_file))
|
||||
{
|
||||
rdr.BaseStream.Position = _dataOffset;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Compression.BSA
|
||||
{
|
||||
@ -31,7 +32,7 @@ namespace Compression.BSA
|
||||
public class FileStateObject
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public string Path { get; set; }
|
||||
public RelativePath Path { get; set; }
|
||||
}
|
||||
|
||||
public interface IFile
|
||||
@ -39,7 +40,7 @@ namespace Compression.BSA
|
||||
/// <summary>
|
||||
/// The path of the file inside the archive
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
RelativePath Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The uncompressed file size
|
||||
|
@ -48,7 +48,7 @@ namespace Compression.BSA
|
||||
{
|
||||
if (bw.BaseStream.Position != orgPos + state.NameOffset)
|
||||
throw new InvalidDataException("Offsets don't match when writing TES3 BSA");
|
||||
bw.Write(Encoding.ASCII.GetBytes(state.Path));
|
||||
bw.Write(Encoding.ASCII.GetBytes((string)state.Path));
|
||||
bw.Write((byte)0);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Compression.BSA
|
||||
{
|
||||
@ -13,12 +14,12 @@ namespace Compression.BSA
|
||||
private uint _fileCount;
|
||||
private TES3FileEntry[] _files;
|
||||
internal long _dataOffset;
|
||||
internal string _filename;
|
||||
internal AbsolutePath _filename;
|
||||
|
||||
public TES3Reader(string filename)
|
||||
public TES3Reader(AbsolutePath filename)
|
||||
{
|
||||
_filename = filename;
|
||||
using var fs = File.OpenRead(filename);
|
||||
using var fs = filename.OpenRead();
|
||||
using var br = new BinaryReader(fs);
|
||||
_versionNumber = br.ReadUInt32();
|
||||
_hashTableOffset = br.ReadUInt32();
|
||||
@ -46,7 +47,7 @@ namespace Compression.BSA
|
||||
for (int i = 0; i < _fileCount; i++)
|
||||
{
|
||||
br.BaseStream.Position = origPos + _files[i].NameOffset;
|
||||
_files[i].Path = br.ReadStringTerm(VersionType.TES3);
|
||||
_files[i].Path = new RelativePath(br.ReadStringTerm(VersionType.TES3));
|
||||
}
|
||||
|
||||
br.BaseStream.Position = _hashTableOffset + 12;
|
||||
@ -95,7 +96,7 @@ namespace Compression.BSA
|
||||
|
||||
public class TES3FileEntry : IFile
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public RelativePath Path { get; set; }
|
||||
public uint Size { get; set; }
|
||||
public FileStateObject State =>
|
||||
new TES3FileState
|
||||
@ -111,7 +112,7 @@ namespace Compression.BSA
|
||||
|
||||
public void CopyDataTo(Stream output)
|
||||
{
|
||||
using var fs = File.OpenRead(Archive._filename);
|
||||
using var fs = Archive._filename.OpenRead();
|
||||
fs.Position = Archive._dataOffset + Offset;
|
||||
fs.CopyToLimit(output, (int)Size);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Wabbajack.Common;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Compression.BSA
|
||||
@ -64,9 +65,9 @@ namespace Compression.BSA
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ToBZString(this string val, VersionType version)
|
||||
public static byte[] ToBZString(this RelativePath val, VersionType version)
|
||||
{
|
||||
var b = GetEncoding(version).GetBytes(val);
|
||||
var b = GetEncoding(version).GetBytes((string)val);
|
||||
var b2 = new byte[b.Length + 2];
|
||||
b.CopyTo(b2, 1);
|
||||
b2[0] = (byte) (b.Length + 1);
|
||||
@ -78,9 +79,9 @@ namespace Compression.BSA
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ToBSString(this string val)
|
||||
public static byte[] ToBSString(this RelativePath val)
|
||||
{
|
||||
var b = Encoding.ASCII.GetBytes(val);
|
||||
var b = Encoding.ASCII.GetBytes((string)val);
|
||||
var b2 = new byte[b.Length + 1];
|
||||
b.CopyTo(b2, 1);
|
||||
b2[0] = (byte) b.Length;
|
||||
@ -102,12 +103,22 @@ namespace Compression.BSA
|
||||
return b2;
|
||||
}
|
||||
|
||||
public static byte[] ToTermString(this RelativePath val, VersionType version)
|
||||
{
|
||||
return ((string)val).ToTermString(version);
|
||||
}
|
||||
|
||||
public static ulong GetBSAHash(this string name)
|
||||
{
|
||||
name = name.Replace('/', '\\');
|
||||
return GetBSAHash(Path.ChangeExtension(name, null), Path.GetExtension(name));
|
||||
}
|
||||
|
||||
public static ulong GetBSAHash(this RelativePath name)
|
||||
{
|
||||
return ((string)name).GetBSAHash();
|
||||
}
|
||||
|
||||
public static ulong GetBSAHash(this string name, string ext)
|
||||
{
|
||||
name = name.ToLowerInvariant();
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
@ -20,17 +21,22 @@ namespace Wabbajack.Common
|
||||
|
||||
public static string MegaPrefix = "https://mega.nz/#!";
|
||||
|
||||
public static HashSet<string> SupportedArchives = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {".zip", ".rar", ".7z", ".7zip", ".fomod", ".omod", ".exe", ".dat"};
|
||||
public static readonly HashSet<Extension> SupportedArchives = new[]{".zip", ".rar", ".7z", ".7zip", ".fomod", ".omod", ".exe", ".dat"}
|
||||
.Select(s => new Extension(s)).ToHashSet();
|
||||
|
||||
// HashSet with archive extensions that need to be tested before extraction
|
||||
public static HashSet<string> TestArchivesBeforeExtraction = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {".dat"};
|
||||
public static HashSet<Extension> TestArchivesBeforeExtraction = new []{".dat"}.Select(s => new Extension(s)).ToHashSet();
|
||||
|
||||
public static HashSet<string> SupportedBSAs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {".bsa", ".ba2"};
|
||||
public static readonly HashSet<Extension> SupportedBSAs = new[] {".bsa", ".ba2"}
|
||||
.Select(s => new Extension(s)).ToHashSet();
|
||||
|
||||
public static HashSet<string> ConfigFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {".json", ".ini", ".yml", ".xml"};
|
||||
public static HashSet<string> ESPFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".esp", ".esm", ".esl"};
|
||||
public static HashSet<string> AssetFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {".dds", ".tga", ".nif", ".psc", ".pex"};
|
||||
|
||||
public static readonly Extension EXE = new Extension(".exe");
|
||||
public static readonly Extension OMOD = new Extension(".omod");
|
||||
|
||||
public static string NexusCacheDirectory = "nexus_link_cache";
|
||||
|
||||
public static string WABBAJACK_INCLUDE = "WABBAJACK_INCLUDE";
|
||||
|
14
Wabbajack.Common/Enumerable.cs
Normal file
14
Wabbajack.Common/Enumerable.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
public static partial class Utils
|
||||
{
|
||||
public static IEnumerable<T> Cons<T>(this IEnumerable<T> coll, T next)
|
||||
{
|
||||
yield return next;
|
||||
foreach (var itm in coll) yield return itm;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -111,13 +111,13 @@ namespace Wabbajack.Common
|
||||
return sha.Hash.ToHex();
|
||||
}
|
||||
|
||||
public static Hash FileHash(this string file, bool nullOnIoError = false)
|
||||
public static Hash FileHash(this AbsolutePath file, bool nullOnIoError = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(file);
|
||||
using var fs = file.OpenRead();
|
||||
var config = new xxHashConfig {HashSizeInBits = 64};
|
||||
using var f = new StatusFileStream(fs, $"Hashing {Path.GetFileName(file)}");
|
||||
using var f = new StatusFileStream(fs, $"Hashing {(string)file.FileName}");
|
||||
return new Hash(BitConverter.ToUInt64(xxHashFactory.Instance.Create(config).ComputeHash(f).Hash));
|
||||
}
|
||||
catch (IOException)
|
||||
@ -127,7 +127,7 @@ namespace Wabbajack.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static Hash FileHashCached(this string file, bool nullOnIoError = false)
|
||||
public static Hash FileHashCached(this AbsolutePath file, bool nullOnIoError = false)
|
||||
{
|
||||
if (TryGetHashCache(file, out var foundHash)) return foundHash;
|
||||
|
||||
@ -137,7 +137,7 @@ namespace Wabbajack.Common
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static bool TryGetHashCache(string file, out Hash hash)
|
||||
public static bool TryGetHashCache(AbsolutePath file, out Hash hash)
|
||||
{
|
||||
var hashFile = file + Consts.HashFileExtension;
|
||||
hash = Hash.Empty;
|
||||
@ -151,24 +151,24 @@ namespace Wabbajack.Common
|
||||
if (version != HashCacheVersion) return false;
|
||||
|
||||
var lastModified = br.ReadUInt64();
|
||||
if (lastModified != File.GetLastWriteTimeUtc(file).AsUnixTime()) return false;
|
||||
if (lastModified != file.LastModifiedUtc.AsUnixTime()) return false;
|
||||
hash = new Hash(br.ReadUInt64());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private const uint HashCacheVersion = 0x01;
|
||||
private static void WriteHashCache(string file, Hash hash)
|
||||
private static void WriteHashCache(AbsolutePath file, Hash hash)
|
||||
{
|
||||
using var fs = File.Create(file + Consts.HashFileExtension);
|
||||
using var bw = new BinaryWriter(fs);
|
||||
bw.Write(HashCacheVersion);
|
||||
var lastModified = File.GetLastWriteTimeUtc(file).AsUnixTime();
|
||||
var lastModified = file.LastModifiedUtc.AsUnixTime();
|
||||
bw.Write(lastModified);
|
||||
bw.Write((ulong)hash);
|
||||
}
|
||||
|
||||
public static async Task<Hash> FileHashCachedAsync(this string file, bool nullOnIOError = false)
|
||||
public static async Task<Hash> FileHashCachedAsync(this AbsolutePath file, bool nullOnIOError = false)
|
||||
{
|
||||
if (TryGetHashCache(file, out var foundHash)) return foundHash;
|
||||
|
||||
@ -178,11 +178,11 @@ namespace Wabbajack.Common
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static async Task<Hash> FileHashAsync(this string file, bool nullOnIOError = false)
|
||||
public static async Task<Hash> FileHashAsync(this AbsolutePath file, bool nullOnIOError = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var fs = File.OpenRead(file);
|
||||
await using var fs = file.OpenRead();
|
||||
var config = new xxHashConfig {HashSizeInBits = 64};
|
||||
var value = await xxHashFactory.Instance.Create(config).ComputeHashAsync(fs);
|
||||
return new Hash(BitConverter.ToUInt64(value.Hash));
|
||||
|
393
Wabbajack.Common/Paths.cs
Normal file
393
Wabbajack.Common/Paths.cs
Normal file
@ -0,0 +1,393 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Alphaleonis.Win32.Filesystem;
|
||||
using Directory = System.IO.Directory;
|
||||
using File = Alphaleonis.Win32.Filesystem.File;
|
||||
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
public class AbstractPath
|
||||
{
|
||||
|
||||
public RelativePath FileName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case AbsolutePath abs:
|
||||
return abs.FileName;
|
||||
case RelativePath rel:
|
||||
return rel.FileName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class AbsolutePath : AbstractPath
|
||||
{
|
||||
|
||||
#region ObjectEquality
|
||||
protected bool Equals(AbsolutePath other)
|
||||
{
|
||||
return _path == other._path;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj.GetType() != this.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals((AbsolutePath) obj);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (_path != null ? _path.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
private readonly string _path;
|
||||
private Extension _extension;
|
||||
|
||||
public AbsolutePath(string path)
|
||||
{
|
||||
_path = path.ToLowerInvariant().Replace("/", "\\").TrimEnd('\\');
|
||||
ValidateAbsolutePath();
|
||||
}
|
||||
|
||||
public AbsolutePath(string path, bool skipValidation)
|
||||
{
|
||||
_path = path.ToLowerInvariant().Replace("/", "\\").TrimEnd('\\');
|
||||
if (!skipValidation)
|
||||
ValidateAbsolutePath();
|
||||
}
|
||||
|
||||
public AbsolutePath(AbsolutePath path)
|
||||
{
|
||||
_path = path._path;
|
||||
}
|
||||
|
||||
private void ValidateAbsolutePath()
|
||||
{
|
||||
if (Path.IsPathRooted(_path)) return;
|
||||
throw new InvalidDataException($"Absolute path must be absolute");
|
||||
}
|
||||
|
||||
public Extension Extension
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_extension != null) return _extension;
|
||||
var extension = Path.GetExtension(_path);
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
return null;
|
||||
_extension = (Extension)extension;
|
||||
return _extension;
|
||||
}
|
||||
}
|
||||
|
||||
public FileStream OpenRead()
|
||||
{
|
||||
return File.OpenRead(_path);
|
||||
}
|
||||
|
||||
public FileStream Create()
|
||||
{
|
||||
return File.Create(_path);
|
||||
}
|
||||
|
||||
public FileStream OpenWrite()
|
||||
{
|
||||
return File.OpenWrite(_path);
|
||||
}
|
||||
|
||||
public async Task WriteAllTextAsync(string text)
|
||||
{
|
||||
await using var fs = File.Create(_path);
|
||||
await fs.WriteAsync(Encoding.UTF8.GetBytes(text));
|
||||
}
|
||||
|
||||
public bool Exists => File.Exists(_path) || Directory.Exists(_path);
|
||||
public bool IsFile => File.Exists(_path);
|
||||
public bool IsDirectory => Directory.Exists(_path);
|
||||
|
||||
public long Size => (new FileInfo(_path)).Length;
|
||||
|
||||
public DateTime LastModified => File.GetLastWriteTime(_path);
|
||||
public DateTime LastModifiedUtc => File.GetLastWriteTimeUtc(_path);
|
||||
public AbsolutePath Parent => (AbsolutePath)Path.GetDirectoryName(_path);
|
||||
public RelativePath FileName => (RelativePath)Path.GetFileName(_path);
|
||||
public void Copy(AbsolutePath otherPath)
|
||||
{
|
||||
File.Copy(_path, otherPath._path);
|
||||
}
|
||||
|
||||
public void Move(AbsolutePath otherPath, bool overwrite = false)
|
||||
{
|
||||
File.Move(_path, otherPath._path, overwrite ? MoveOptions.ReplaceExisting : MoveOptions.None);
|
||||
}
|
||||
|
||||
public RelativePath RelativeTo(AbsolutePath p)
|
||||
{
|
||||
if (_path.Substring(0, p._path.Length + 1) != p._path + "\\")
|
||||
throw new InvalidDataException("Not a parent path");
|
||||
return new RelativePath(_path.Substring(p._path.Length + 1));
|
||||
}
|
||||
|
||||
public async Task<string> ReadAllTextAsync()
|
||||
{
|
||||
await using var fs = File.OpenRead(_path);
|
||||
return Encoding.UTF8.GetString(await fs.ReadAllAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assuming the path is a folder, enumerate all the files in the folder
|
||||
/// </summary>
|
||||
/// <param name="recursive">if true, also returns files in sub-folders</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<AbsolutePath> EnumerateFiles(bool recursive = true)
|
||||
{
|
||||
return Directory
|
||||
.EnumerateFiles(_path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
||||
.Select(path => new AbsolutePath(path, true));
|
||||
}
|
||||
|
||||
|
||||
#region Operators
|
||||
public static explicit operator string(AbsolutePath path)
|
||||
{
|
||||
return path._path;
|
||||
}
|
||||
|
||||
public static explicit operator AbsolutePath(string path)
|
||||
{
|
||||
return !Path.IsPathRooted(path) ? ((RelativePath)path).RelativeToEntryPoint() : new AbsolutePath(path);
|
||||
}
|
||||
|
||||
public static bool operator ==(AbsolutePath a, AbsolutePath b)
|
||||
{
|
||||
return a._path == b._path;
|
||||
}
|
||||
|
||||
public static bool operator !=(AbsolutePath a, AbsolutePath b)
|
||||
{
|
||||
return a._path != b._path;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void CreateDirectory()
|
||||
{
|
||||
Directory.CreateDirectory(_path);
|
||||
}
|
||||
}
|
||||
|
||||
public class RelativePath : AbstractPath
|
||||
{
|
||||
private readonly string _path;
|
||||
private Extension _extension;
|
||||
|
||||
public RelativePath(string path)
|
||||
{
|
||||
_path = path.ToLowerInvariant().Replace("/", "\\").Trim('\\');
|
||||
Validate();
|
||||
}
|
||||
|
||||
public static RelativePath RandomFileName()
|
||||
{
|
||||
return (RelativePath)Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private void Validate()
|
||||
{
|
||||
if (Path.IsPathRooted(_path))
|
||||
throw new InvalidDataException("Cannot create relative path from absolute path string");
|
||||
}
|
||||
|
||||
public AbsolutePath RelativeTo(AbsolutePath abs)
|
||||
{
|
||||
return new AbsolutePath(Path.Combine((string)abs, _path));
|
||||
}
|
||||
|
||||
public AbsolutePath RelativeToEntryPoint()
|
||||
{
|
||||
return RelativeTo(((AbsolutePath)Assembly.GetEntryAssembly().Location).Parent);
|
||||
}
|
||||
|
||||
public AbsolutePath RelativeToWorkingDirectory()
|
||||
{
|
||||
return RelativeTo((AbsolutePath)Directory.GetCurrentDirectory());
|
||||
}
|
||||
|
||||
public static explicit operator string(RelativePath path)
|
||||
{
|
||||
return path._path;
|
||||
}
|
||||
|
||||
public static explicit operator RelativePath(string path)
|
||||
{
|
||||
return new RelativePath(path);
|
||||
}
|
||||
|
||||
public AbsolutePath RelativeToSystemDirectory()
|
||||
{
|
||||
return RelativeTo((AbsolutePath)Environment.SystemDirectory);
|
||||
}
|
||||
|
||||
public RelativePath Parent => (RelativePath)Path.GetDirectoryName(_path);
|
||||
}
|
||||
|
||||
public class Extension
|
||||
{
|
||||
#region ObjectEquality
|
||||
protected bool Equals(Extension other)
|
||||
{
|
||||
return _extension == other._extension;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj.GetType() != this.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals((Extension) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (_extension != null ? _extension.GetHashCode() : 0);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly string _extension;
|
||||
|
||||
public Extension(string extension)
|
||||
{
|
||||
_extension = string.Intern(extension);
|
||||
Validate();
|
||||
}
|
||||
private void Validate()
|
||||
{
|
||||
if (!_extension.StartsWith("."))
|
||||
throw new InvalidDataException($"Extensions must start with '.'");
|
||||
}
|
||||
|
||||
public static explicit operator string(Extension path)
|
||||
{
|
||||
return path._extension;
|
||||
}
|
||||
|
||||
public static explicit operator Extension(string path)
|
||||
{
|
||||
return new Extension(path);
|
||||
}
|
||||
|
||||
public static bool operator ==(Extension a, Extension b)
|
||||
{
|
||||
// Super fast comparison because extensions are interned
|
||||
return ReferenceEquals(a._extension, b._extension);
|
||||
}
|
||||
|
||||
public static bool operator !=(Extension a, Extension b)
|
||||
{
|
||||
return !ReferenceEquals(a._extension, b._extension);
|
||||
}
|
||||
}
|
||||
|
||||
public class HashRelativePath
|
||||
{
|
||||
public Hash BaseHash { get; }
|
||||
public RelativePath[] Paths { get; }
|
||||
|
||||
public string ToString()
|
||||
{
|
||||
return string.Join("|", Paths.Select(t => t.ToString()).Cons(BaseHash.ToString()));
|
||||
}
|
||||
|
||||
public static bool operator ==(HashRelativePath a, HashRelativePath b)
|
||||
{
|
||||
if (a.BaseHash != b.BaseHash || a.Paths.Length == b.Paths.Length)
|
||||
return false;
|
||||
|
||||
for (int idx = 0; idx < a.Paths.Length; idx += 1)
|
||||
if (a.Paths[idx] != b.Paths[idx])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(HashRelativePath a, HashRelativePath b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
public class FullPath
|
||||
{
|
||||
public AbsolutePath Base { get; }
|
||||
public RelativePath[] Paths { get; }
|
||||
|
||||
public FullPath(AbsolutePath basePath, RelativePath[] paths)
|
||||
{
|
||||
Base = basePath;
|
||||
Paths = Paths;
|
||||
}
|
||||
|
||||
public string ToString()
|
||||
{
|
||||
return string.Join("|", Paths.Select(t => t.ToString()).Cons(Base.ToString()));
|
||||
}
|
||||
|
||||
public static bool operator ==(FullPath a, FullPath b)
|
||||
{
|
||||
if (a.Base != b.Base || a.Paths.Length == b.Paths.Length)
|
||||
return false;
|
||||
|
||||
for (int idx = 0; idx < a.Paths.Length; idx += 1)
|
||||
if (a.Paths[idx] != b.Paths[idx])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(FullPath a, FullPath b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,21 +8,21 @@ namespace Wabbajack.Common.StatusFeed.Errors
|
||||
{
|
||||
public class _7zipReturnError : AErrorMessage
|
||||
{
|
||||
public string Destination { get; }
|
||||
public string Filename;
|
||||
public AbsolutePath Destination { get; }
|
||||
public AbsolutePath Filename { get; }
|
||||
public int Code;
|
||||
public string _7zip_output;
|
||||
public override string ShortDescription => $"7Zip returned an error while executing";
|
||||
|
||||
public override string ExtendedDescription =>
|
||||
$@"7Zip.exe should always return 0 when it finishes executing. While extracting {Filename} 7Zip encountered some error and
|
||||
$@"7Zip.exe should always return 0 when it finishes executing. While extracting {(string)Filename} 7Zip encountered some error and
|
||||
instead returned {Code} which indicates there was an error. The archive might be corrupt or in a format that 7Zip cannot handle. Please verify the file is valid and that you
|
||||
haven't run out of disk space in the {Destination} folder.
|
||||
haven't run out of disk space in the {(string)Destination} folder.
|
||||
|
||||
7Zip Output:
|
||||
{_7zip_output}";
|
||||
|
||||
public _7zipReturnError(int code, string filename, string destination, string output)
|
||||
public _7zipReturnError(int code, AbsolutePath filename, AbsolutePath destination, string output)
|
||||
{
|
||||
Code = code;
|
||||
Filename = filename;
|
||||
|
@ -9,36 +9,32 @@ namespace Wabbajack.Common
|
||||
{
|
||||
public class TempFolder : IDisposable
|
||||
{
|
||||
public DirectoryInfo Dir { get; private set; }
|
||||
public AbsolutePath Dir { get; }
|
||||
public bool DeleteAfter = true;
|
||||
|
||||
public TempFolder(bool deleteAfter = true)
|
||||
{
|
||||
this.Dir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
|
||||
this.Dir.Create();
|
||||
this.DeleteAfter = deleteAfter;
|
||||
Dir = new AbsolutePath(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
|
||||
if (!Dir.Exists)
|
||||
Dir.CreateDirectory();
|
||||
DeleteAfter = deleteAfter;
|
||||
}
|
||||
|
||||
public TempFolder(DirectoryInfo dir, bool deleteAfter = true)
|
||||
public TempFolder(AbsolutePath dir, bool deleteAfter = true)
|
||||
{
|
||||
this.Dir = dir;
|
||||
Dir = dir;
|
||||
if (!dir.Exists)
|
||||
{
|
||||
this.Dir.Create();
|
||||
Dir.Create();
|
||||
}
|
||||
this.DeleteAfter = deleteAfter;
|
||||
}
|
||||
|
||||
public TempFolder(string addedFolderPath, bool deleteAfter = true)
|
||||
: this(new DirectoryInfo(Path.Combine(Path.GetTempPath(), addedFolderPath)), deleteAfter: deleteAfter)
|
||||
{
|
||||
DeleteAfter = deleteAfter;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (DeleteAfter)
|
||||
if (DeleteAfter && Dir.Exists)
|
||||
{
|
||||
Utils.DeleteDirectory(this.Dir.FullName);
|
||||
Utils.DeleteDirectory(Dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1039,12 +1039,12 @@ namespace Wabbajack.Common
|
||||
/// delete a folder. If you don't like this code, it's unlikely to change without a ton of testing.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public static async void DeleteDirectory(string path)
|
||||
public static async void DeleteDirectory(AbsolutePath path)
|
||||
{
|
||||
var process = new ProcessHelper
|
||||
{
|
||||
Path = "cmd.exe",
|
||||
Arguments = new object[] {"/c", "del", "/f", "/q", "/s", $"\"{path}\"", "&&", "rmdir", "/q", "/s", $"\"{path}\""},
|
||||
Arguments = new object[] {"/c", "del", "/f", "/q", "/s", $"\"{(string)path}\"", "&&", "rmdir", "/q", "/s", $"\"{(string)path}\""},
|
||||
};
|
||||
var result = process.Output.Where(d => d.Type == ProcessHelper.StreamType.Output)
|
||||
.ForEachAsync(p =>
|
||||
|
@ -22,12 +22,12 @@ namespace Wabbajack.VirtualFileSystem
|
||||
static Context()
|
||||
{
|
||||
Utils.Log("Cleaning VFS, this may take a bit of time");
|
||||
Utils.DeleteDirectory(_stagingFolder);
|
||||
Utils.DeleteDirectory(StagingFolder);
|
||||
}
|
||||
public const ulong FileVersion = 0x03;
|
||||
public const string Magic = "WABBAJACK VFS FILE";
|
||||
|
||||
private static readonly string _stagingFolder = "vfs_staging";
|
||||
private static readonly AbsolutePath StagingFolder = ((RelativePath)"vfs_staging").RelativeToWorkingDirectory();
|
||||
public IndexRoot Index { get; private set; } = IndexRoot.Empty;
|
||||
|
||||
/// <summary>
|
||||
@ -47,23 +47,18 @@ namespace Wabbajack.VirtualFileSystem
|
||||
Queue = queue;
|
||||
UseExtendedHashes = extendedHashes;
|
||||
}
|
||||
|
||||
|
||||
public TemporaryDirectory GetTemporaryFolder()
|
||||
public static TemporaryDirectory GetTemporaryFolder()
|
||||
{
|
||||
return new TemporaryDirectory(Path.Combine(_stagingFolder, Guid.NewGuid().ToString()));
|
||||
return new TemporaryDirectory(((RelativePath)Guid.NewGuid().ToString()).RelativeTo(StagingFolder));
|
||||
}
|
||||
|
||||
public async Task<IndexRoot> AddRoot(string root)
|
||||
public async Task<IndexRoot> AddRoot(AbsolutePath root)
|
||||
{
|
||||
if (!Path.IsPathRooted(root))
|
||||
throw new InvalidDataException($"Path is not absolute: {root}");
|
||||
|
||||
var filtered = Index.AllFiles.Where(file => File.Exists(file.Name)).ToList();
|
||||
var filtered = Index.AllFiles.Where(file => file.IsNative && ((AbsolutePath) file.Name).Exists).ToList();
|
||||
|
||||
var byPath = filtered.ToImmutableDictionary(f => f.Name);
|
||||
|
||||
var filesToIndex = Directory.EnumerateFiles(root, "*", DirectoryEnumerationOptions.Recursive).Distinct().ToList();
|
||||
var filesToIndex = root.EnumerateFiles().Distinct().ToList();
|
||||
|
||||
var results = Channel.Create(1024, ProgressUpdater<VirtualFile>($"Indexing {root}", filesToIndex.Count));
|
||||
|
||||
@ -72,8 +67,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
{
|
||||
if (byPath.TryGetValue(f, out var found))
|
||||
{
|
||||
var fi = new FileInfo(f);
|
||||
if (found.LastModified == fi.LastWriteTimeUtc.Ticks && found.Size == fi.Length)
|
||||
if (found.LastModified == f.LastModifiedUtc.AsUnixTime() && found.Size == f.Size)
|
||||
return found;
|
||||
}
|
||||
|
||||
@ -90,27 +84,21 @@ namespace Wabbajack.VirtualFileSystem
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
public async Task<IndexRoot> AddRoots(List<string> roots)
|
||||
public async Task<IndexRoot> AddRoots(List<AbsolutePath> roots)
|
||||
{
|
||||
if (!roots.All(p => Path.IsPathRooted(p)))
|
||||
throw new InvalidDataException($"Paths are not absolute");
|
||||
var native = Index.AllFiles.Where(file => file.IsNative).ToDictionary(file => file.StagedPath);
|
||||
|
||||
var filtered = Index.AllFiles.Where(file => File.Exists(file.Name)).ToList();
|
||||
var filtered = Index.AllFiles.Where(file => ((AbsolutePath)file.Name).Exists).ToList();
|
||||
|
||||
var byPath = filtered.ToImmutableDictionary(f => f.Name);
|
||||
|
||||
var filesToIndex = roots.SelectMany(root => Directory.EnumerateFiles(root, "*", DirectoryEnumerationOptions.Recursive)).ToList();
|
||||
|
||||
var results = Channel.Create(1024, ProgressUpdater<VirtualFile>($"Indexing roots", filesToIndex.Count));
|
||||
var filesToIndex = roots.SelectMany(root => root.EnumerateFiles()).ToList();
|
||||
|
||||
var allFiles = await filesToIndex
|
||||
.PMap(Queue, async f =>
|
||||
{
|
||||
Utils.Status($"Indexing {Path.GetFileName(f)}");
|
||||
if (byPath.TryGetValue(f, out var found))
|
||||
Utils.Status($"Indexing {Path.GetFileName((string)f)}");
|
||||
if (native.TryGetValue(f, out var found))
|
||||
{
|
||||
var fi = new FileInfo(f);
|
||||
if (found.LastModified == fi.LastWriteTimeUtc.Ticks && found.Size == fi.Length)
|
||||
if (found.LastModified == f.LastModifiedUtc.AsUnixTime() && found.Size == f.Size)
|
||||
return found;
|
||||
}
|
||||
|
||||
@ -224,7 +212,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
var tmpPath = Path.Combine(_stagingFolder, Guid.NewGuid().ToString());
|
||||
var tmpPath = Path.Combine(StagingFolder, Guid.NewGuid().ToString());
|
||||
await FileExtractor.ExtractAll(Queue, group.Key.StagedPath, tmpPath);
|
||||
paths.Add(tmpPath);
|
||||
foreach (var file in group)
|
||||
@ -354,10 +342,10 @@ namespace Wabbajack.VirtualFileSystem
|
||||
public static IndexRoot Empty = new IndexRoot();
|
||||
|
||||
public IndexRoot(ImmutableList<VirtualFile> aFiles,
|
||||
ImmutableDictionary<string, VirtualFile> byFullPath,
|
||||
ImmutableDictionary<FullPath, VirtualFile> byFullPath,
|
||||
ImmutableDictionary<Hash, ImmutableStack<VirtualFile>> byHash,
|
||||
ImmutableDictionary<string, VirtualFile> byRoot,
|
||||
ImmutableDictionary<string, ImmutableStack<VirtualFile>> byName)
|
||||
ImmutableDictionary<AbsolutePath, VirtualFile> byRoot,
|
||||
ImmutableDictionary<AbstractPath, ImmutableStack<VirtualFile>> byName)
|
||||
{
|
||||
AllFiles = aFiles;
|
||||
ByFullPath = byFullPath;
|
||||
@ -369,18 +357,18 @@ namespace Wabbajack.VirtualFileSystem
|
||||
public IndexRoot()
|
||||
{
|
||||
AllFiles = ImmutableList<VirtualFile>.Empty;
|
||||
ByFullPath = ImmutableDictionary<string, VirtualFile>.Empty;
|
||||
ByFullPath = ImmutableDictionary<FullPath, VirtualFile>.Empty;
|
||||
ByHash = ImmutableDictionary<Hash, ImmutableStack<VirtualFile>>.Empty;
|
||||
ByRootPath = ImmutableDictionary<string, VirtualFile>.Empty;
|
||||
ByName = ImmutableDictionary<string, ImmutableStack<VirtualFile>>.Empty;
|
||||
ByRootPath = ImmutableDictionary<AbsolutePath, VirtualFile>.Empty;
|
||||
ByName = ImmutableDictionary<AbstractPath, ImmutableStack<VirtualFile>>.Empty;
|
||||
}
|
||||
|
||||
|
||||
public ImmutableList<VirtualFile> AllFiles { get; }
|
||||
public ImmutableDictionary<string, VirtualFile> ByFullPath { get; }
|
||||
public ImmutableDictionary<FullPath, VirtualFile> ByFullPath { get; }
|
||||
public ImmutableDictionary<Hash, ImmutableStack<VirtualFile>> ByHash { get; }
|
||||
public ImmutableDictionary<string, ImmutableStack<VirtualFile>> ByName { get; set; }
|
||||
public ImmutableDictionary<string, VirtualFile> ByRootPath { get; }
|
||||
public ImmutableDictionary<AbstractPath, ImmutableStack<VirtualFile>> ByName { get; set; }
|
||||
public ImmutableDictionary<AbsolutePath, VirtualFile> ByRootPath { get; }
|
||||
|
||||
public async Task<IndexRoot> Integrate(ICollection<VirtualFile> files)
|
||||
{
|
||||
@ -397,7 +385,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
var byName = Task.Run(() => allFiles.SelectMany(f => f.ThisAndAllChildren)
|
||||
.ToGroupedImmutableDictionary(f => f.Name));
|
||||
|
||||
var byRootPath = Task.Run(() => allFiles.ToImmutableDictionary(f => f.Name));
|
||||
var byRootPath = Task.Run(() => allFiles.ToImmutableDictionary(f => f.Name as AbsolutePath));
|
||||
|
||||
var result = new IndexRoot(allFiles,
|
||||
await byFullPath,
|
||||
@ -408,27 +396,27 @@ namespace Wabbajack.VirtualFileSystem
|
||||
return result;
|
||||
}
|
||||
|
||||
public VirtualFile FileForArchiveHashPath(string[] argArchiveHashPath)
|
||||
public VirtualFile FileForArchiveHashPath(HashRelativePath argArchiveHashPath)
|
||||
{
|
||||
var cur = ByHash[Hash.FromBase64(argArchiveHashPath[0])].First(f => f.Parent == null);
|
||||
return argArchiveHashPath.Skip(1).Aggregate(cur, (current, itm) => ByName[itm].First(f => f.Parent == current));
|
||||
var cur = ByHash[argArchiveHashPath.BaseHash].First(f => f.Parent == null);
|
||||
return argArchiveHashPath.Paths.Aggregate(cur, (current, itm) => ByName[itm].First(f => f.Parent == current));
|
||||
}
|
||||
}
|
||||
|
||||
public class TemporaryDirectory : IDisposable
|
||||
{
|
||||
public TemporaryDirectory(string name)
|
||||
public TemporaryDirectory(AbsolutePath name)
|
||||
{
|
||||
FullName = name;
|
||||
if (!Directory.Exists(FullName))
|
||||
Directory.CreateDirectory(FullName);
|
||||
if (!FullName.Exists)
|
||||
FullName.CreateDirectory();
|
||||
}
|
||||
|
||||
public string FullName { get; }
|
||||
public AbsolutePath FullName { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(FullName))
|
||||
if (FullName.Exists)
|
||||
Utils.DeleteDirectory(FullName);
|
||||
}
|
||||
}
|
||||
|
@ -16,15 +16,15 @@ namespace Wabbajack.VirtualFileSystem
|
||||
public class FileExtractor
|
||||
{
|
||||
|
||||
public static async Task ExtractAll(WorkQueue queue, string source, string dest)
|
||||
public static async Task ExtractAll(WorkQueue queue, AbsolutePath source, AbsolutePath dest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Consts.SupportedBSAs.Any(b => source.ToLower().EndsWith(b)))
|
||||
if (Consts.SupportedBSAs.Contains(source.Extension))
|
||||
await ExtractAllWithBSA(queue, source, dest);
|
||||
else if (source.EndsWith(".omod"))
|
||||
else if (source.Extension == Consts.OMOD)
|
||||
ExtractAllWithOMOD(source, dest);
|
||||
else if (source.EndsWith(".exe"))
|
||||
else if (source.Extension == Consts.EXE)
|
||||
ExtractAllWithInno(source, dest);
|
||||
else
|
||||
ExtractAllWith7Zip(source, dest);
|
||||
@ -35,14 +35,14 @@ namespace Wabbajack.VirtualFileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractAllWithInno(string source, string dest)
|
||||
private static void ExtractAllWithInno(AbsolutePath source, AbsolutePath dest)
|
||||
{
|
||||
Utils.Log($"Extracting {Path.GetFileName(source)}");
|
||||
Utils.Log($"Extracting {(string)source.FileName}");
|
||||
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = @"Extractors\innounp.exe",
|
||||
Arguments = $"-x -y -b -d\"{dest}\" \"{source}\"",
|
||||
Arguments = $"-x -y -b -d\"{(string)dest}\" \"{(string)source}\"",
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
@ -64,7 +64,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
Utils.Error(e, "Error while setting process priority level for innounp.exe");
|
||||
}
|
||||
|
||||
var name = Path.GetFileName(source);
|
||||
var name = source.FileName;
|
||||
try
|
||||
{
|
||||
while (!p.HasExited)
|
||||
@ -77,7 +77,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
continue;
|
||||
|
||||
int.TryParse(line.Substring(0, 3), out var percentInt);
|
||||
Utils.Status($"Extracting {name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d));
|
||||
Utils.Status($"Extracting {(string)name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -85,7 +85,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
Utils.Error(e, "Error while reading StandardOutput for innounp.exe");
|
||||
}
|
||||
|
||||
p.WaitForExitAndWarn(TimeSpan.FromSeconds(30), $"Extracting {name}");
|
||||
p.WaitForExitAndWarn(TimeSpan.FromSeconds(30), $"Extracting {(string)name}");
|
||||
if (p.ExitCode == 0)
|
||||
return;
|
||||
|
||||
@ -113,59 +113,52 @@ namespace Wabbajack.VirtualFileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractAllWithOMOD(string source, string dest)
|
||||
private static void ExtractAllWithOMOD(AbsolutePath source, AbsolutePath dest)
|
||||
{
|
||||
Utils.Log($"Extracting {Path.GetFileName(source)}");
|
||||
Utils.Log($"Extracting {(string)source.FileName}");
|
||||
|
||||
Framework.Settings.TempPath = dest;
|
||||
Framework.Settings.TempPath = (string)dest;
|
||||
Framework.Settings.CodeProgress = new OMODProgress();
|
||||
|
||||
var omod = new OMOD(source);
|
||||
var omod = new OMOD((string)source);
|
||||
omod.GetDataFiles();
|
||||
omod.GetPlugins();
|
||||
}
|
||||
|
||||
|
||||
private static async Task ExtractAllWithBSA(WorkQueue queue, string source, string dest)
|
||||
private static async Task ExtractAllWithBSA(WorkQueue queue, AbsolutePath source, AbsolutePath dest)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var arch = BSADispatch.OpenRead(source))
|
||||
{
|
||||
using var arch = BSADispatch.OpenRead(source);
|
||||
await arch.Files
|
||||
.PMap(queue, f =>
|
||||
{
|
||||
var path = f.Path;
|
||||
if (f.Path.StartsWith("\\"))
|
||||
path = f.Path.Substring(1);
|
||||
Utils.Status($"Extracting {path}");
|
||||
var outPath = Path.Combine(dest, path);
|
||||
var parent = Path.GetDirectoryName(outPath);
|
||||
Utils.Status($"Extracting {(string)f.Path}");
|
||||
var outPath = f.Path.RelativeTo(dest);
|
||||
var parent = outPath.Parent;
|
||||
|
||||
if (!Directory.Exists(parent))
|
||||
Directory.CreateDirectory(parent);
|
||||
if (!parent.IsDirectory)
|
||||
parent.CreateDirectory();
|
||||
|
||||
using (var fs = File.Open(outPath, System.IO.FileMode.Create))
|
||||
{
|
||||
using var fs = outPath.Create();
|
||||
f.CopyDataTo(fs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.ErrorThrow(ex, $"While Extracting {source}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractAllWith7Zip(string source, string dest)
|
||||
private static void ExtractAllWith7Zip(AbsolutePath source, AbsolutePath dest)
|
||||
{
|
||||
Utils.Log(new GenericInfo($"Extracting {Path.GetFileName(source)}", $"The contents of {source} are being extracted to {dest} using 7zip.exe"));
|
||||
Utils.Log(new GenericInfo($"Extracting {(string)source.FileName}", $"The contents of {(string)source.FileName} are being extracted to {(string)source.FileName} using 7zip.exe"));
|
||||
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = @"Extractors\7z.exe",
|
||||
Arguments = $"x -bsp1 -y -o\"{dest}\" \"{source}\" -mmt=off",
|
||||
Arguments = $"x -bsp1 -y -o\"{(string)dest}\" \"{(string)source}\" -mmt=off",
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
@ -185,7 +178,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
{
|
||||
}
|
||||
|
||||
var name = Path.GetFileName(source);
|
||||
var name = source.FileName;
|
||||
try
|
||||
{
|
||||
while (!p.HasExited)
|
||||
@ -197,7 +190,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
if (line.Length <= 4 || line[3] != '%') continue;
|
||||
|
||||
int.TryParse(line.Substring(0, 3), out var percentInt);
|
||||
Utils.Status($"Extracting {name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d));
|
||||
Utils.Status($"Extracting {(string)name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d));
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@ -219,13 +212,13 @@ namespace Wabbajack.VirtualFileSystem
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
/// <returns></returns>
|
||||
public static bool CanExtract(string v)
|
||||
public static bool CanExtract(AbsolutePath v)
|
||||
{
|
||||
var ext = Path.GetExtension(v.ToLower());
|
||||
if(ext != ".exe" && !Consts.TestArchivesBeforeExtraction.Contains(ext))
|
||||
var ext = v.Extension;
|
||||
if(ext != _exeExtension && !Consts.TestArchivesBeforeExtraction.Contains(ext))
|
||||
return Consts.SupportedArchives.Contains(ext) || Consts.SupportedBSAs.Contains(ext);
|
||||
|
||||
if (ext == ".exe")
|
||||
if (ext == _exeExtension)
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
@ -243,7 +236,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
p.Start();
|
||||
ChildProcessTracker.AddProcess(p);
|
||||
|
||||
var name = Path.GetFileName(v);
|
||||
var name = v.FileName;
|
||||
while (!p.HasExited)
|
||||
{
|
||||
var line = p.StandardOutput.ReadLine();
|
||||
@ -253,7 +246,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
if (line[0] != '#')
|
||||
continue;
|
||||
|
||||
Utils.Status($"Testing {name} - {line.Trim()}");
|
||||
Utils.Status($"Testing {(string)name} - {line.Trim()}");
|
||||
}
|
||||
|
||||
p.WaitForExitAndWarn(TimeSpan.FromSeconds(30), $"Testing {name}");
|
||||
@ -299,10 +292,12 @@ namespace Wabbajack.VirtualFileSystem
|
||||
}
|
||||
|
||||
|
||||
public static bool MightBeArchive(string path)
|
||||
private static Extension _exeExtension = new Extension(".exe");
|
||||
|
||||
public static bool MightBeArchive(AbsolutePath path)
|
||||
{
|
||||
var ext = Path.GetExtension(path.ToLower());
|
||||
return ext == ".exe" || Consts.SupportedArchives.Contains(ext) || Consts.SupportedBSAs.Contains(ext);
|
||||
var ext = path.Extension;
|
||||
return ext == _exeExtension || Consts.SupportedArchives.Contains(ext) || Consts.SupportedBSAs.Contains(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
/// </summary>
|
||||
public class IndexedVirtualFile
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AbstractPath Name { get; set; }
|
||||
public Hash Hash { get; set; }
|
||||
public long Size { get; set; }
|
||||
public List<IndexedVirtualFile> Children { get; set; } = new List<IndexedVirtualFile>();
|
||||
|
@ -4,7 +4,7 @@ namespace Wabbajack.VirtualFileSystem
|
||||
{
|
||||
public class PortableFile
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AbstractPath Name { get; set; }
|
||||
public Hash Hash { get; set; }
|
||||
public Hash ParentHash { get; set; }
|
||||
public long Size { get; set; }
|
||||
|
@ -18,26 +18,26 @@ namespace Wabbajack.VirtualFileSystem
|
||||
{
|
||||
public class VirtualFile
|
||||
{
|
||||
private string _fullPath;
|
||||
private FullPath _fullPath;
|
||||
|
||||
private string _stagedPath;
|
||||
public string Name { get; internal set; }
|
||||
private AbsolutePath _stagedPath;
|
||||
public AbstractPath Name { get; internal set; }
|
||||
|
||||
public string FullPath
|
||||
public FullPath FullPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fullPath != null) return _fullPath;
|
||||
|
||||
var cur = this;
|
||||
var acc = new LinkedList<string>();
|
||||
var acc = new LinkedList<AbstractPath>();
|
||||
while (cur != null)
|
||||
{
|
||||
acc.AddFirst(cur.Name);
|
||||
cur = cur.Parent;
|
||||
}
|
||||
|
||||
_fullPath = string.Join("|", acc);
|
||||
|
||||
_fullPath = new FullPath(acc.First() as AbsolutePath, acc.Skip(1).OfType<RelativePath>().ToArray());
|
||||
return _fullPath;
|
||||
}
|
||||
}
|
||||
@ -46,20 +46,20 @@ namespace Wabbajack.VirtualFileSystem
|
||||
public ExtendedHashes ExtendedHashes { get; set; }
|
||||
public long Size { get; internal set; }
|
||||
|
||||
public long LastModified { get; internal set; }
|
||||
public ulong LastModified { get; internal set; }
|
||||
|
||||
public long LastAnalyzed { get; internal set; }
|
||||
public ulong LastAnalyzed { get; internal set; }
|
||||
|
||||
public VirtualFile Parent { get; internal set; }
|
||||
|
||||
public Context Context { get; set; }
|
||||
|
||||
public string StagedPath
|
||||
public AbsolutePath StagedPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsNative)
|
||||
return Name;
|
||||
return Name as AbsolutePath;
|
||||
if (_stagedPath == null)
|
||||
throw new UnstagedFileException(FullPath);
|
||||
return _stagedPath;
|
||||
@ -147,20 +147,19 @@ namespace Wabbajack.VirtualFileSystem
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<VirtualFile> Analyze(Context context, VirtualFile parent, string abs_path,
|
||||
string rel_path, bool topLevel)
|
||||
public static async Task<VirtualFile> Analyze(Context context, VirtualFile parent, AbsolutePath absPath,
|
||||
AbstractPath relPath, bool topLevel)
|
||||
{
|
||||
var hash = abs_path.FileHash();
|
||||
var fi = new FileInfo(abs_path);
|
||||
var hash = absPath.FileHash();
|
||||
|
||||
if (!context.UseExtendedHashes && FileExtractor.MightBeArchive(abs_path))
|
||||
if (!context.UseExtendedHashes && FileExtractor.MightBeArchive(absPath))
|
||||
{
|
||||
var result = await TryGetContentsFromServer(hash);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
Utils.Log($"Downloaded VFS data for {Path.GetFileName(abs_path)}");
|
||||
VirtualFile Convert(IndexedVirtualFile file, string path, VirtualFile vparent)
|
||||
Utils.Log($"Downloaded VFS data for {(string)absPath}");
|
||||
VirtualFile Convert(IndexedVirtualFile file, AbstractPath path, VirtualFile vparent)
|
||||
{
|
||||
var vself = new VirtualFile
|
||||
{
|
||||
@ -168,8 +167,8 @@ namespace Wabbajack.VirtualFileSystem
|
||||
Name = path,
|
||||
Parent = vparent,
|
||||
Size = file.Size,
|
||||
LastModified = fi.LastWriteTimeUtc.Ticks,
|
||||
LastAnalyzed = DateTime.Now.Ticks,
|
||||
LastModified = absPath.LastModifiedUtc.AsUnixTime(),
|
||||
LastAnalyzed = DateTime.Now.AsUnixTime(),
|
||||
Hash = file.Hash,
|
||||
|
||||
};
|
||||
@ -179,32 +178,32 @@ namespace Wabbajack.VirtualFileSystem
|
||||
return vself;
|
||||
}
|
||||
|
||||
return Convert(result, rel_path, parent);
|
||||
return Convert(result, relPath, parent);
|
||||
}
|
||||
}
|
||||
|
||||
var self = new VirtualFile
|
||||
{
|
||||
Context = context,
|
||||
Name = rel_path,
|
||||
Name = relPath,
|
||||
Parent = parent,
|
||||
Size = fi.Length,
|
||||
LastModified = fi.LastWriteTimeUtc.Ticks,
|
||||
LastAnalyzed = DateTime.Now.Ticks,
|
||||
Size = absPath.Size,
|
||||
LastModified = absPath.LastModifiedUtc.AsUnixTime(),
|
||||
LastAnalyzed = DateTime.Now.AsUnixTime(),
|
||||
Hash = hash
|
||||
};
|
||||
if (context.UseExtendedHashes)
|
||||
self.ExtendedHashes = ExtendedHashes.FromFile(abs_path);
|
||||
self.ExtendedHashes = ExtendedHashes.FromFile(absPath);
|
||||
|
||||
if (FileExtractor.CanExtract(abs_path))
|
||||
if (FileExtractor.CanExtract(absPath))
|
||||
{
|
||||
|
||||
using (var tempFolder = context.GetTemporaryFolder())
|
||||
using (var tempFolder = Context.GetTemporaryFolder())
|
||||
{
|
||||
await FileExtractor.ExtractAll(context.Queue, abs_path, tempFolder.FullName);
|
||||
await FileExtractor.ExtractAll(context.Queue, absPath, tempFolder.FullName);
|
||||
|
||||
var list = await Directory.EnumerateFiles(tempFolder.FullName, "*", SearchOption.AllDirectories)
|
||||
.PMap(context.Queue, abs_src => Analyze(context, self, abs_src, abs_src.RelativeTo(tempFolder.FullName), false));
|
||||
var list = await tempFolder.FullName.EnumerateFiles()
|
||||
.PMap(context.Queue, absSrc => Analyze(context, self, absSrc, absSrc.RelativeTo(tempFolder.FullName), false));
|
||||
|
||||
self.Children = list.ToImmutableList();
|
||||
}
|
||||
@ -236,59 +235,29 @@ namespace Wabbajack.VirtualFileSystem
|
||||
}
|
||||
|
||||
|
||||
public void Write(MemoryStream ms)
|
||||
private void Write(Stream stream)
|
||||
{
|
||||
using (var bw = new BinaryWriter(ms, Encoding.UTF8, true))
|
||||
{
|
||||
Write(bw);
|
||||
}
|
||||
}
|
||||
|
||||
private void Write(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Name);
|
||||
bw.Write(FullPath);
|
||||
bw.Write(Hash);
|
||||
bw.Write(Size);
|
||||
bw.Write(LastModified);
|
||||
bw.Write(LastAnalyzed);
|
||||
bw.Write(Children.Count);
|
||||
foreach (var child in Children)
|
||||
child.Write(bw);
|
||||
stream.WriteAsMessagePack(this);
|
||||
}
|
||||
|
||||
public static VirtualFile Read(Context context, byte[] data)
|
||||
{
|
||||
using (var ms = new MemoryStream(data))
|
||||
using (var br = new BinaryReader(ms))
|
||||
{
|
||||
return Read(context, null, br);
|
||||
}
|
||||
using var ms = new MemoryStream(data);
|
||||
return Read(context, null, ms);
|
||||
}
|
||||
|
||||
private static VirtualFile Read(Context context, VirtualFile parent, BinaryReader br)
|
||||
private static VirtualFile Read(Context context, VirtualFile parent, Stream br)
|
||||
{
|
||||
var vf = new VirtualFile
|
||||
{
|
||||
Context = context,
|
||||
Parent = parent,
|
||||
Name = br.ReadString(),
|
||||
_fullPath = br.ReadString(),
|
||||
Hash = br.ReadHash(),
|
||||
Size = br.ReadInt64(),
|
||||
LastModified = br.ReadInt64(),
|
||||
LastAnalyzed = br.ReadInt64(),
|
||||
Children = ImmutableList<VirtualFile>.Empty
|
||||
};
|
||||
|
||||
var childrenCount = br.ReadInt32();
|
||||
for (var idx = 0; idx < childrenCount; idx += 1) vf.Children = vf.Children.Add(Read(context, vf, br));
|
||||
var vf = br.ReadAsMessagePack<VirtualFile>();
|
||||
vf.Parent = parent;
|
||||
vf.Context = context;
|
||||
vf.Children ??= ImmutableList<VirtualFile>.Empty;
|
||||
|
||||
return vf;
|
||||
}
|
||||
|
||||
public static VirtualFile CreateFromPortable(Context context,
|
||||
Dictionary<Hash, IEnumerable<PortableFile>> state, Dictionary<Hash, string> links,
|
||||
Dictionary<Hash, IEnumerable<PortableFile>> state, Dictionary<Hash, AbsolutePath> links,
|
||||
PortableFile portableFile)
|
||||
{
|
||||
var vf = new VirtualFile
|
||||
@ -337,16 +306,16 @@ namespace Wabbajack.VirtualFileSystem
|
||||
|
||||
public FileStream OpenRead()
|
||||
{
|
||||
return File.OpenRead(StagedPath);
|
||||
return StagedPath.OpenRead();
|
||||
}
|
||||
}
|
||||
|
||||
public class ExtendedHashes
|
||||
{
|
||||
public static ExtendedHashes FromFile(string file)
|
||||
public static ExtendedHashes FromFile(AbsolutePath file)
|
||||
{
|
||||
var hashes = new ExtendedHashes();
|
||||
using (var stream = File.OpenRead(file))
|
||||
using (var stream = file.OpenRead())
|
||||
{
|
||||
hashes.SHA256 = System.Security.Cryptography.SHA256.Create().ComputeHash(stream).ToHex();
|
||||
stream.Position = 0;
|
||||
@ -386,9 +355,9 @@ namespace Wabbajack.VirtualFileSystem
|
||||
|
||||
public class UnstagedFileException : Exception
|
||||
{
|
||||
private readonly string _fullPath;
|
||||
private readonly FullPath _fullPath;
|
||||
|
||||
public UnstagedFileException(string fullPath) : base($"File {fullPath} is unstaged, cannot get staged name")
|
||||
public UnstagedFileException(FullPath fullPath) : base($"File {fullPath} is unstaged, cannot get staged name")
|
||||
{
|
||||
_fullPath = fullPath;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user