diff --git a/Wabbajack.Common/Consts.cs b/Wabbajack.Common/Consts.cs index 12142972..56ab64c6 100644 --- a/Wabbajack.Common/Consts.cs +++ b/Wabbajack.Common/Consts.cs @@ -30,12 +30,14 @@ namespace Wabbajack.Common 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 HashSet<Extension> ConfigFileExtensions = new[]{".json", ".ini", ".yml", ".xml"}.Select(s => new Extension(s)).ToHashSet(); + public static HashSet<Extension> ESPFileExtensions = new []{ ".esp", ".esm", ".esl"}.Select(s => new Extension(s)).ToHashSet(); + public static HashSet<Extension> AssetFileExtensions = new[] {".dds", ".tga", ".nif", ".psc", ".pex"}.Select(s => new Extension(s)).ToHashSet(); public static readonly Extension EXE = new Extension(".exe"); public static readonly Extension OMOD = new Extension(".omod"); + public static readonly Extension ESM = new Extension(".esm"); + public static readonly Extension ESP = new Extension(".esp"); public static string NexusCacheDirectory = "nexus_link_cache"; @@ -59,7 +61,7 @@ namespace Wabbajack.Common public static string AppName = "Wabbajack"; - public static HashSet<string> GameESMs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + public static HashSet<RelativePath> GameESMs = new [] { // Skyrim LE/SE "Skyrim.esm", @@ -77,7 +79,7 @@ namespace Wabbajack.Common "DLCNukaWorld.esm", "DLCUltraHighResolution.esm" - }; + }.Select(s => (RelativePath)s).ToHashSet(); public static string ModPermissionsURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/NexusModPermissions.yml"; public static string ServerWhitelistURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/ServerWhitelist.yml"; @@ -93,11 +95,13 @@ namespace Wabbajack.Common return headerString; } } + + public static RelativePath MetaIni = new RelativePath("meta.ini"); - public static string HashFileExtension => ".xxHash"; - public static string MetaFileExtension => ".meta"; - public static string ModListExtension = ".wabbajack"; - public static string LocalAppDataPath => Path.Combine(KnownFolders.LocalAppData.Path, "Wabbajack"); + public static Extension HashFileExtension = new Extension(".xxHash"); + public static Extension MetaFileExtension = new Extension(".meta"); + public static Extension ModListExtension = new Extension(".wabbajack"); + public static AbsolutePath LocalAppDataPath => new AbsolutePath(Path.Combine(KnownFolders.LocalAppData.Path, "Wabbajack")); public static string MetricsKeyHeader => "x-metrics-key"; public static string WabbajackCacheLocation = "http://build.wabbajack.org/nexus_api_cache/"; @@ -107,13 +111,16 @@ namespace Wabbajack.Common public static int MaxHTTPRetries = 4; public const string MO2ModFolderName = "mods"; - public static string PatchCacheFolder => Path.Combine(LocalAppDataPath, "patch_cache"); + public static AbsolutePath PatchCacheFolder => LocalAppDataPath.Combine("patch_cache"); public static int MaxConnectionsPerServer = 4; - public static string LogsFolder = "logs"; + public static AbsolutePath LogsFolder = ((RelativePath)"logs").RelativeToEntryPoint(); + public static AbsolutePath EntryPoint = (AbsolutePath)(Assembly.GetEntryAssembly()?.Location ?? (string)((RelativePath)"Unknown").RelativeToWorkingDirectory()); + public static AbsolutePath LogFile = LogsFolder.Combine(EntryPoint.FileNameWithoutExtension + ".current.log"); public static int MaxOldLogs = 50; + public static Extension BSA = new Extension(".BSA"); - public static string SettingsFile => Path.Combine(LocalAppDataPath, "settings.json"); + public static AbsolutePath SettingsFile => LocalAppDataPath.Combine("settings.json"); public static byte SettingsVersion => 1; } } diff --git a/Wabbajack.Common/Hash.cs b/Wabbajack.Common/Hash.cs index aa44138b..2350e037 100644 --- a/Wabbajack.Common/Hash.cs +++ b/Wabbajack.Common/Hash.cs @@ -139,13 +139,13 @@ namespace Wabbajack.Common public static bool TryGetHashCache(AbsolutePath file, out Hash hash) { - var hashFile = file + Consts.HashFileExtension; + var hashFile = file.WithExtension(Consts.HashFileExtension); hash = Hash.Empty; - if (!File.Exists(hashFile)) return false; + if (!hashFile.IsFile) return false; - if (File.GetSize(hashFile) != 20) return false; + if (hashFile.Size != 20) return false; - using var fs = File.OpenRead(hashFile); + using var fs = hashFile.OpenRead(); using var br = new BinaryReader(fs); var version = br.ReadUInt32(); if (version != HashCacheVersion) return false; @@ -160,7 +160,7 @@ namespace Wabbajack.Common private const uint HashCacheVersion = 0x01; private static void WriteHashCache(AbsolutePath file, Hash hash) { - using var fs = File.Create(file + Consts.HashFileExtension); + using var fs = file.WithExtension(Consts.HashFileExtension).Create(); using var bw = new BinaryWriter(fs); bw.Write(HashCacheVersion); var lastModified = file.LastModifiedUtc.AsUnixTime(); diff --git a/Wabbajack.Common/Paths.cs b/Wabbajack.Common/Paths.cs index f92c1613..0f37b193 100644 --- a/Wabbajack.Common/Paths.cs +++ b/Wabbajack.Common/Paths.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -16,13 +15,18 @@ namespace Wabbajack.Common { public interface IPath { + /// <summary> + /// Get the final file name, for c:\bar\baz this is `baz` for c:\bar.zip this is `bar.zip` + /// for `bar.zip` this is `bar.zip` + /// </summary> + public RelativePath FileName { get; } } - + public struct AbsolutePath : IPath { - #region ObjectEquality - bool Equals(AbsolutePath other) + + private bool Equals(AbsolutePath other) { return _path == other._path; } @@ -39,51 +43,57 @@ namespace Wabbajack.Common return true; } - if (obj.GetType() != this.GetType()) + if (obj.GetType() != GetType()) { return false; } - return Equals((AbsolutePath) obj); + return Equals((AbsolutePath)obj); } + #endregion public override int GetHashCode() { - return (_path != null ? _path.GetHashCode() : 0); + return _path != null ? _path.GetHashCode() : 0; } private readonly string _path; - private Extension _extension; public AbsolutePath(string path) { _path = path.ToLowerInvariant().Replace("/", "\\").TrimEnd('\\'); - _extension = new Extension(Path.GetExtension(_path)); + Extension = new Extension(Path.GetExtension(_path)); ValidateAbsolutePath(); } public AbsolutePath(string path, bool skipValidation) { _path = path.ToLowerInvariant().Replace("/", "\\").TrimEnd('\\'); - _extension = Extension.FromPath(path); - if (!skipValidation) + Extension = Extension.FromPath(path); + if (!skipValidation) + { ValidateAbsolutePath(); + } } public AbsolutePath(AbsolutePath path) { _path = path._path; - _extension = path._extension; + Extension = path.Extension; } private void ValidateAbsolutePath() { - if (Path.IsPathRooted(_path)) return; - throw new InvalidDataException($"Absolute path must be absolute"); + if (Path.IsPathRooted(_path)) + { + return; + } + + throw new InvalidDataException("Absolute path must be absolute"); } - public Extension Extension => _extension; + public Extension Extension { get; } public FileStream OpenRead() { @@ -112,23 +122,22 @@ namespace Wabbajack.Common public void DeleteDirectory() { - if (IsDirectory) - Utils.DeleteDirectory(this); + if (IsDirectory) + { + Utils.DeleteDirectory(this); + } } - - public long Size => (new FileInfo(_path)).Length; + + 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 RelativePath FileNameWithoutExtension => (RelativePath)Path.GetFileNameWithoutExtension(_path); /// <summary> - /// Moves this file to the specified location + /// Moves this file to the specified location /// </summary> /// <param name="otherPath"></param> /// <param name="overwrite">Replace the destination file if it exists</param> @@ -139,11 +148,14 @@ namespace Wabbajack.Common public RelativePath RelativeTo(AbsolutePath p) { - if (_path.Substring(0, p._path.Length + 1) != p._path + "\\") + 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); @@ -151,7 +163,7 @@ namespace Wabbajack.Common } /// <summary> - /// Assuming the path is a folder, enumerate all the files in the folder + /// 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> @@ -164,25 +176,27 @@ namespace Wabbajack.Common #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() @@ -193,25 +207,117 @@ namespace Wabbajack.Common public void Delete() { if (IsFile) + { File.Delete(_path); + } + } + + public bool InFolder(AbsolutePath gameFolder) + { + throw new NotImplementedException(); + } + + public async Task<byte[]> ReadAllBytesAsync() + { + await using var f = OpenRead(); + return await f.ReadAllAsync(); + } + + public AbsolutePath WithExtension(Extension hashFileExtension) + { + return new AbsolutePath(_path + (string)Extension, true); + } + + public AbsolutePath ReplaceExtension(Extension extension) + { + return new AbsolutePath( + Path.Combine(Path.GetDirectoryName(_path), Path.GetFileNameWithoutExtension(_path) + (string)extension), + true); + } + + public AbsolutePath AppendToName(AbsolutePath bsa, string toAppend) + { + return new AbsolutePath( + Path.Combine(Path.GetDirectoryName(_path), + Path.GetFileNameWithoutExtension(_path) + toAppend + (string)Extension)); + } + + public AbsolutePath Combine(params RelativePath[] paths) + { + return new AbsolutePath(Path.Combine(paths.Select(s => (string)s).Cons(_path).ToArray())); + } + + public AbsolutePath Combine(params string[] paths) + { + return new AbsolutePath(Path.Combine(paths.Cons(_path).ToArray())); + } + + public IEnumerable<string> ReadAllLines() + { + return File.ReadAllLines(_path); + } + + public void WriteAllBytes(byte[] data) + { + using var fs = Create(); + fs.Write(data); + } + + public async Task WriteAllBytesAsync(byte[] data) + { + await using var fs = Create(); + await fs.WriteAsync(data); + } + + public void AppendAllText(string text) + { + File.AppendAllText(_path, text); + } + + public void CopyTo(AbsolutePath dest, bool useMove = false) + { + if (useMove) + { + File.Move(_path, dest._path); + } + else + { + File.Copy(_path, dest._path); + } + } + + public async Task<IEnumerable<string>> ReadAllLinesAsync() + { + return (await ReadAllTextAsync()).Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); + } + + public byte[] ReadAllBytes() + { + return File.ReadAllBytes(_path); } } public struct RelativePath : IPath, IEquatable<RelativePath> { private readonly string _path; - private Extension _extension; public RelativePath(string path) { _path = path.ToLowerInvariant().Replace("/", "\\").Trim('\\'); - _extension = new Extension(Path.GetExtension(path)); + Extension = new Extension(Path.GetExtension(path)); Validate(); } + public override string ToString() + { + return _path; + } + + public Extension Extension { get; } + public override int GetHashCode() { - return (_path != null ? _path.GetHashCode() : 0); + return _path != null ? _path.GetHashCode() : 0; } public static RelativePath RandomFileName() @@ -222,7 +328,9 @@ namespace Wabbajack.Common private void Validate() { if (Path.IsPathRooted(_path)) + { throw new InvalidDataException("Cannot create relative path from absolute path string"); + } } public AbsolutePath RelativeTo(AbsolutePath abs) @@ -234,17 +342,17 @@ namespace Wabbajack.Common { 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); @@ -254,9 +362,9 @@ namespace Wabbajack.Common { return RelativeTo((AbsolutePath)Environment.SystemDirectory); } - + public RelativePath Parent => (RelativePath)Path.GetDirectoryName(_path); - + public RelativePath FileName => new RelativePath(Path.GetFileName(_path)); public bool Equals(RelativePath other) @@ -268,16 +376,21 @@ namespace Wabbajack.Common { return obj is RelativePath other && Equals(other); } - + public static bool operator ==(RelativePath a, RelativePath b) { return a._path == b._path; } - + public static bool operator !=(RelativePath a, RelativePath b) { return !(a == b); } + + public bool StartsWith(string s) + { + return _path.StartsWith(s); + } } public static partial class Utils @@ -286,7 +399,7 @@ namespace Wabbajack.Common { return (RelativePath)str; } - + public static AbsolutePath RelativeTo(this string str, AbsolutePath path) { return ((RelativePath)str).RelativeTo(path); @@ -296,15 +409,20 @@ namespace Wabbajack.Common { wtr.Write(path is AbsolutePath); if (path is AbsolutePath) + { wtr.Write((AbsolutePath)path); + } else + { wtr.Write((RelativePath)path); + } } public static void Write(this BinaryWriter wtr, AbsolutePath path) { wtr.Write((string)path); } + public static void Write(this BinaryWriter wtr, RelativePath path) { wtr.Write((string)path); @@ -313,7 +431,10 @@ namespace Wabbajack.Common public static IPath ReadIPath(this BinaryReader rdr) { if (rdr.ReadBoolean()) + { return rdr.ReadAbsolutePath(); + } + return rdr.ReadRelativePath(); } @@ -334,15 +455,15 @@ namespace Wabbajack.Common newArr[arr.Length] = itm; return newArr; } - } public struct Extension { - public static Extension None = new Extension("", false); - + public static Extension None = new Extension("", false); + #region ObjectEquality - bool Equals(Extension other) + + private bool Equals(Extension other) { return _extension == other._extension; } @@ -359,18 +480,19 @@ namespace Wabbajack.Common return true; } - if (obj.GetType() != this.GetType()) + if (obj.GetType() != GetType()) { return false; } - return Equals((Extension) obj); + return Equals((Extension)obj); } public override int GetHashCode() { - return (_extension != null ? _extension.GetHashCode() : 0); + return _extension != null ? _extension.GetHashCode() : 0; } + #endregion private readonly string _extension; @@ -390,39 +512,51 @@ namespace Wabbajack.Common private Extension(string extension, bool validate) { _extension = string.Intern(extension); - if (validate) Validate(); - + if (validate) + { + Validate(); + } } public Extension(Extension other) { _extension = other._extension; } - + private void Validate() { if (!_extension.StartsWith(".")) - throw new InvalidDataException($"Extensions must start with '.'"); + { + 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 - if ((object)a == null && (object)b == null) return true; - if ((object)a == null || (object)b == null) return false; + if ((object)a == null && (object)b == null) + { + return true; + } + + if ((object)a == null || (object)b == null) + { + return false; + } + return ReferenceEquals(a._extension, b._extension); } - + public static bool operator !=(Extension a, Extension b) { return !(a == b); @@ -445,7 +579,7 @@ namespace Wabbajack.Common { EMPTY_PATH = new RelativePath[0]; } - + public HashRelativePath(Hash baseHash, params RelativePath[] paths) { BaseHash = baseHash; @@ -456,25 +590,31 @@ namespace Wabbajack.Common { 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) + } + + for (var 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 struct FullPath : IEquatable<FullPath> { public AbsolutePath Base { get; } @@ -488,7 +628,9 @@ namespace Wabbajack.Common Paths = paths; _hash = Base.GetHashCode(); foreach (var itm in Paths) + { _hash ^= itm.GetHashCode(); + } } public override string ToString() @@ -504,15 +646,21 @@ namespace Wabbajack.Common 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) + } + + for (var 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); diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index 931f3797..4cfa9093 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -38,8 +38,8 @@ namespace Wabbajack.Common return processList.Where(process => process.ProcessName == "ModOrganizer").Any(process => Path.GetDirectoryName(process.MainModule?.FileName) == mo2Path); } - public static string LogFile { get; } - public static string LogFolder { get; } + public static AbsolutePath LogFile { get; } + public static AbsolutePath LogFolder { get; } public enum FileEventType { @@ -52,34 +52,28 @@ namespace Wabbajack.Common { MessagePackInit(); - if (!Directory.Exists(Consts.LocalAppDataPath)) - Directory.CreateDirectory(Consts.LocalAppDataPath); + Consts.LocalAppDataPath.CreateDirectory(); + Consts.LogsFolder.CreateDirectory(); - if (!Directory.Exists(Consts.LogsFolder)) - Directory.CreateDirectory(Consts.LogsFolder); - - var programName = Assembly.GetEntryAssembly()?.Location ?? "Wabbajack"; - LogFolder = Path.Combine(Path.GetDirectoryName(programName), Consts.LogsFolder); - LogFile = Path.Combine(Consts.LogsFolder, Path.GetFileNameWithoutExtension(programName) + ".current.log"); + LogFolder = Consts.LogsFolder; + LogFile = Consts.LogFile; _startTime = DateTime.Now; - if (LogFile.FileExists()) + if (LogFile.Exists) { - var newPath = Path.Combine(Consts.LogsFolder, Path.GetFileNameWithoutExtension(programName) + new FileInfo(LogFile).LastWriteTime.ToString(" yyyy-MM-dd HH_mm_ss") + ".log"); - File.Move(LogFile, newPath, MoveOptions.ReplaceExisting); + var newPath = Consts.LogsFolder.Combine(Consts.EntryPoint.FileNameWithoutExtension + LogFile.LastModified.ToString(" yyyy-MM-dd HH_mm_ss") + ".log"); + LogFile.MoveTo(newPath, true); } - var logFiles = Directory.GetFiles(Consts.LogsFolder); - if (logFiles.Length >= Consts.MaxOldLogs) + var logFiles = Consts.LogsFolder.EnumerateFiles(false).ToList(); + if (logFiles.Count >= Consts.MaxOldLogs) { - Log($"Maximum amount of old logs reached ({logFiles.Length} >= {Consts.MaxOldLogs})"); + Log($"Maximum amount of old logs reached ({logFiles.Count} >= {Consts.MaxOldLogs})"); var filesToDelete = logFiles - .Where(File.Exists) - .OrderBy(f => - { - var fi = new FileInfo(f); - return fi.LastWriteTime; - }).Take(logFiles.Length - Consts.MaxOldLogs).ToList(); + .Where(f => f.IsFile) + .OrderBy(f => f.LastModified) + .Take(logFiles.Count - Consts.MaxOldLogs) + .ToList(); Log($"Found {filesToDelete.Count} old log files to delete"); @@ -89,7 +83,7 @@ namespace Wabbajack.Common { try { - File.Delete(f); + f.Delete(); success++; } catch (Exception e) @@ -102,7 +96,7 @@ namespace Wabbajack.Common Log($"Deleted {success} log files, failed to delete {failed} logs"); } - var watcher = new FileSystemWatcher(Consts.LocalAppDataPath); + var watcher = new FileSystemWatcher((string)Consts.LocalAppDataPath); AppLocalEvents = Observable.Merge(Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Changed += h, h => watcher.Changed -= h).Select(e => (FileEventType.Changed, e.EventArgs)), Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Created += h, h => watcher.Created -= h).Select(e => (FileEventType.Created, e.EventArgs)), Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(h => watcher.Deleted += h, h => watcher.Deleted -= h).Select(e => (FileEventType.Deleted, e.EventArgs))) @@ -164,7 +158,7 @@ namespace Wabbajack.Common { lock (_lock) { - File.AppendAllText(LogFile, $"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}\r\n"); + LogFile.AppendAllText($"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}\r\n"); } } @@ -328,9 +322,9 @@ namespace Wabbajack.Common /// </summary> /// <param name="file"></param> /// <returns></returns> - public static dynamic LoadIniFile(this string file) + public static dynamic LoadIniFile(this AbsolutePath file) { - return new DynamicIniData(new FileIniDataParser().ReadFile(file)); + return new DynamicIniData(new FileIniDataParser().ReadFile((string)file)); } /// <summary> @@ -749,18 +743,17 @@ namespace Wabbajack.Common { var dataA = a.xxHash().FromBase64().ToHex(); var dataB = b.xxHash().FromBase64().ToHex(); - var cacheFile = Path.Combine(Consts.PatchCacheFolder, $"{dataA}_{dataB}.patch"); - if (!Directory.Exists(Consts.PatchCacheFolder)) - Directory.CreateDirectory(Consts.PatchCacheFolder); + var cacheFile = Consts.PatchCacheFolder.Combine($"{dataA}_{dataB}.patch"); + Consts.PatchCacheFolder.CreateDirectory(); while (true) { - if (File.Exists(cacheFile)) + if (cacheFile.IsFile) { RETRY_OPEN: try { - await using var f = File.OpenRead(cacheFile); + await using var f = cacheFile.OpenRead(); await f.CopyToAsync(output); } catch (IOException) @@ -773,9 +766,9 @@ namespace Wabbajack.Common } else { - var tmpName = Path.Combine(Consts.PatchCacheFolder, Guid.NewGuid() + ".tmp"); + var tmpName = Consts.PatchCacheFolder.Combine(Guid.NewGuid() + ".tmp"); - await using (var f = File.Open(tmpName, System.IO.FileMode.Create)) + await using (var f = tmpName.Create()) { Status("Creating Patch"); OctoDiff.Create(a, b, f); @@ -784,12 +777,11 @@ namespace Wabbajack.Common RETRY: try { - - File.Move(tmpName, cacheFile, MoveOptions.ReplaceExisting); + tmpName.MoveTo(cacheFile, true); } catch (UnauthorizedAccessException) { - if (File.Exists(cacheFile)) + if (cacheFile.IsFile) continue; await Task.Delay(1000); goto RETRY; @@ -808,9 +800,9 @@ namespace Wabbajack.Common await using var sigFile = new TempStream(); OctoDiff.Create(srcStream, destStream, sigFile, patchStream); patchStream.Position = 0; - var tmpName = Path.Combine(Consts.PatchCacheFolder, Guid.NewGuid() + ".tmp"); + var tmpName = Consts.PatchCacheFolder.Combine(Guid.NewGuid() + ".tmp"); - await using (var f = File.Create(tmpName)) + await using (var f = tmpName.Create()) { await patchStream.CopyToAsync(f); patchStream.Position = 0; @@ -818,26 +810,23 @@ namespace Wabbajack.Common try { - var cacheFile = Path.Combine(Consts.PatchCacheFolder, $"{srcHash.ToHex()}_{destHash.ToHex()}.patch"); - if (!Directory.Exists(Consts.PatchCacheFolder)) - Directory.CreateDirectory(Consts.PatchCacheFolder); + var cacheFile = Consts.PatchCacheFolder.Combine($"{srcHash.ToHex()}_{destHash.ToHex()}.patch"); + Consts.PatchCacheFolder.CreateDirectory(); - File.Move(tmpName, cacheFile, MoveOptions.ReplaceExisting); + tmpName.MoveTo(cacheFile, true); } catch (UnauthorizedAccessException) { - if (File.Exists(tmpName)) - File.Delete(tmpName); + tmpName.Delete(); } } public static bool TryGetPatch(Hash foundHash, Hash fileHash, out byte[] ePatch) { - var patchName = Path.Combine(Consts.PatchCacheFolder, - $"{foundHash.ToHex()}_{fileHash.ToHex()}.patch"); - if (File.Exists(patchName)) + var patchName = Consts.PatchCacheFolder.Combine($"{foundHash.ToHex()}_{fileHash.ToHex()}.patch"); + if (patchName.Exists) { - ePatch = File.ReadAllBytes(patchName); + ePatch = patchName.ReadAllBytes(); return true; } @@ -1084,42 +1073,35 @@ namespace Wabbajack.Common public static void ToEcryptedData(this byte[] bytes, string key) { var encoded = ProtectedData.Protect(bytes, Encoding.UTF8.GetBytes(key), DataProtectionScope.LocalMachine); + Consts.LocalAppDataPath.CreateDirectory(); - if (!Directory.Exists(Consts.LocalAppDataPath)) - Directory.CreateDirectory(Consts.LocalAppDataPath); - - var path = Path.Combine(Consts.LocalAppDataPath, key); - File.WriteAllBytes(path, encoded); + Consts.LocalAppDataPath.Combine(key).WriteAllBytes(bytes); } public static byte[] FromEncryptedData(string key) { - var path = Path.Combine(Consts.LocalAppDataPath, key); - var bytes = File.ReadAllBytes(path); + var bytes = Consts.LocalAppDataPath.Combine(key).ReadAllBytes(); return ProtectedData.Unprotect(bytes, Encoding.UTF8.GetBytes(key), DataProtectionScope.LocalMachine); } public static bool HaveEncryptedJson(string key) { - var path = Path.Combine(Consts.LocalAppDataPath, key); - return File.Exists(path); + return Consts.LocalAppDataPath.Combine(key).IsFile; } public static IObservable<(FileEventType, FileSystemEventArgs)> AppLocalEvents { get; } public static IObservable<bool> HaveEncryptedJsonObservable(string key) { - var path = Path.Combine(Consts.LocalAppDataPath, key).ToLower(); - return AppLocalEvents.Where(t => t.Item2.FullPath.ToLower() == path) - .Select(_ => File.Exists(path)) - .StartWith(File.Exists(path)) + var path = Consts.LocalAppDataPath.Combine(key); + return AppLocalEvents.Where(t => (AbsolutePath)t.Item2.FullPath.ToLower() == path) + .Select(_ => path.Exists) + .StartWith(path.Exists) .DistinctUntilChanged(); } public static void DeleteEncryptedJson(string key) { - var path = Path.Combine(Consts.LocalAppDataPath, key); - if (File.Exists(path)) - File.Delete(path); + Consts.LocalAppDataPath.Combine(key).Delete(); } public static void StartProcessFromFile(string file) diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index 81bf812d..e6927b18 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -19,11 +19,12 @@ namespace Wabbajack.Lib { public abstract class ACompiler : ABatchProcessor { - public string ModListName, ModListAuthor, ModListDescription, ModListImage, ModListWebsite, ModListReadme; + public string ModListName, ModListAuthor, ModListDescription, ModListWebsite; + public RelativePath ModListImage, ModListReadme; public bool ReadmeIsWebsite; protected Version WabbajackVersion; - public abstract string VFSCacheName { get; } + public abstract AbsolutePath VFSCacheName { get; } //protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin"); /// <summary> /// A stream of tuples of ("Update Title", 0.25) which represent the name of the current task @@ -34,10 +35,10 @@ namespace Wabbajack.Lib public abstract ModManager ModManager { get; } - public abstract string GamePath { get; } + public abstract AbsolutePath GamePath { get; } - public abstract string ModListOutputFolder { get; } - public abstract string ModListOutputFile { get; } + public abstract AbsolutePath ModListOutputFolder { get; } + public abstract AbsolutePath ModListOutputFile { get; } public bool IgnoreMissingFiles { get; set; } @@ -65,23 +66,35 @@ namespace Wabbajack.Lib throw new Exception(msg); } - internal string IncludeFile(byte[] data) + internal RelativePath IncludeId() { - var id = Guid.NewGuid().ToString(); - File.WriteAllBytes(Path.Combine(ModListOutputFolder, id), data); + return RelativePath.RandomFileName(); + } + + internal async Task<RelativePath> IncludeFile(byte[] data) + { + var id = IncludeId(); + await ModListOutputFolder.Combine(id).WriteAllBytesAsync(data); return id; } - internal FileStream IncludeFile(out string id) + internal FileStream IncludeFile(out RelativePath id) { - id = Guid.NewGuid().ToString(); - return File.Create(Path.Combine(ModListOutputFolder, id)); + id = IncludeId(); + return ModListOutputFolder.Combine(id).Create(); } - internal string IncludeFile(string data) + internal async Task<RelativePath> IncludeFile(string data) { - var id = Guid.NewGuid().ToString(); - File.WriteAllText(Path.Combine(ModListOutputFolder, id), data); + var id = IncludeId(); + await ModListOutputFolder.Combine(id).WriteAllTextAsync(data); + return id; + } + + internal RelativePath IncludeFile(AbsolutePath data) + { + var id = IncludeId(); + data.Copy(ModListOutputFolder.Combine(id)); return id; } diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs index ee0e4c80..978cc727 100644 --- a/Wabbajack.Lib/AInstaller.cs +++ b/Wabbajack.Lib/AInstaller.cs @@ -20,18 +20,18 @@ namespace Wabbajack.Lib { public bool IgnoreMissingFiles { get; internal set; } = false; - public string OutputFolder { get; private set; } - public string DownloadFolder { get; private set; } + public AbsolutePath OutputFolder { get; private set; } + public AbsolutePath DownloadFolder { get; private set; } public abstract ModManager ModManager { get; } public string ModListArchive { get; private set; } public ModList ModList { get; private set; } - public Dictionary<Hash, string> HashedArchives { get; set; } + public Dictionary<Hash, AbsolutePath> HashedArchives { get; set; } public SystemParameters SystemParameters { get; set; } - public AInstaller(string archive, ModList modList, string outputFolder, string downloadFolder, SystemParameters parameters) + public AInstaller(string archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) { ModList = modList; ModListArchive = archive; @@ -90,20 +90,9 @@ namespace Wabbajack.Lib /// We don't want to make the installer index all the archives, that's just a waste of time, so instead /// we'll pass just enough information to VFS to let it know about the files we have. /// </summary> - public async Task PrimeVFS() + protected async Task PrimeVFS() { - VFS.AddKnown(HashedArchives.Select(a => new KnownFile - { - Paths = new[] { a.Value }, - Hash = a.Key - })); - - - VFS.AddKnown( - ModList.Directives - .OfType<FromArchive>() - .Select(f => new KnownFile { Paths = f.ArchiveHashPath, Hash = f.Hash})); - + VFS.AddKnown(ModList.Directives.OfType<FromArchive>().Select(d => d.ArchiveHashPath), HashedArchives); await VFS.BackfillMissing(); } @@ -111,13 +100,9 @@ namespace Wabbajack.Lib { Info("Building Folder Structure"); ModList.Directives - .Select(d => Path.Combine(OutputFolder, Path.GetDirectoryName(d.To))) + .Select(d => OutputFolder.Combine(d.To.Parent)) .Distinct() - .Do(f => - { - if (Directory.Exists(f)) return; - Directory.CreateDirectory(f); - }); + .Do(f => OutputFolder.CreateDirectory()); } public async Task InstallArchives() @@ -126,8 +111,8 @@ namespace Wabbajack.Lib Info("Grouping Install Files"); var grouped = ModList.Directives .OfType<FromArchive>() - .GroupBy(e => e.ArchiveHashPath[0]) - .ToDictionary(k => Hash.FromBase64(k.Key)); + .GroupBy(e => e.ArchiveHashPath.BaseHash) + .ToDictionary(k => k.Key); var archives = ModList.Archives .Select(a => new { Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash) }) .Where(a => a.AbsolutePath != null) @@ -137,7 +122,7 @@ namespace Wabbajack.Lib await archives.PMap(Queue, UpdateTracker,a => InstallArchive(Queue, a.Archive, a.AbsolutePath, grouped[a.Archive.Hash])); } - private async Task InstallArchive(WorkQueue queue, Archive archive, string absolutePath, IGrouping<string, FromArchive> grouping) + private async Task InstallArchive(WorkQueue queue, Archive archive, AbsolutePath absolutePath, IGrouping<Hash, FromArchive> grouping) { Status($"Extracting {archive.Name}"); @@ -184,13 +169,12 @@ namespace Wabbajack.Lib .PDoIndexed(queue, (idx, group) => { Utils.Status("Installing files", Percent.FactoryPutInRange(idx, vFiles.Count)); - var firstDest = Path.Combine(OutputFolder, group.First().To); - CopyFile(group.Key.StagedPath, firstDest, true); + var firstDest = OutputFolder.Combine(group.First().To); + group.Key.StagedPath.CopyTo(firstDest, true); foreach (var copy in group.Skip(1)) { - var nextDest = Path.Combine(OutputFolder, copy.To); - CopyFile(firstDest, nextDest, false); + firstDest.CopyTo(OutputFolder.Combine(copy.To)); } }); @@ -203,25 +187,25 @@ namespace Wabbajack.Lib .PMap(queue, async toPatch => { await using var patchStream = new MemoryStream(); - Status($"Patching {Path.GetFileName(toPatch.To)}"); + Status($"Patching {toPatch.To.FileName}"); // Read in the patch data byte[] patchData = LoadBytesFromPath(toPatch.PatchID); - var toFile = Path.Combine(OutputFolder, toPatch.To); - var oldData = new MemoryStream(File.ReadAllBytes(toFile)); + var toFile = OutputFolder.Combine(toPatch.To); + var oldData = new MemoryStream(await toFile.ReadAllBytesAsync()); // Remove the file we're about to patch - File.Delete(toFile); + toFile.Delete(); // Patch it - await using (var outStream = File.Open(toFile, FileMode.Create)) + await using (var outStream = toFile.Create()) { Utils.ApplyPatch(oldData, () => new MemoryStream(patchData), outStream); } - Status($"Verifying Patch {Path.GetFileName(toPatch.To)}"); - var resultSha = toFile.FileHash(); + Status($"Verifying Patch {toPatch.To.FileName}"); + var resultSha = await toFile.FileHashAsync(); if (resultSha != toPatch.Hash) throw new InvalidDataException($"Invalid Hash for {toPatch.To} after patching"); }); diff --git a/Wabbajack.Lib/CompilationSteps/CompilationErrors/InvalidGameESMError.cs b/Wabbajack.Lib/CompilationSteps/CompilationErrors/InvalidGameESMError.cs index 4bb367c3..3a362505 100644 --- a/Wabbajack.Lib/CompilationSteps/CompilationErrors/InvalidGameESMError.cs +++ b/Wabbajack.Lib/CompilationSteps/CompilationErrors/InvalidGameESMError.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps.CompilationErrors public Hash Hash { get; } public string PathToFile { get; } private readonly CleanedESM _esm; - public string GameFileName => Path.GetFileName(_esm.To); + public RelativePath GameFileName => _esm.To.FileName; public override string ShortDescription { get => @@ -24,7 +24,7 @@ namespace Wabbajack.Lib.CompilationSteps.CompilationErrors public override string ExtendedDescription { get => - $@"This modlist is setup to perform automatic cleaning of the stock game file {GameFileName} in order to perform this cleaning Wabbajack must first verify that the + $@"This modlist is setup to perform automatic cleaning of the stock game file {(string)GameFileName} in order to perform this cleaning Wabbajack must first verify that the source file is in the correct state. It seems that the file in your game directory has a hash of {Hash} instead of the expect hash of {_esm.SourceESMHash}. This could be caused by the modlist expecting a different of the game than you currently have installed, or perhaps you have already cleaned the file. You could attempt to fix this error by re-installing the game, and then attempting to re-install this modlist. Also verify that the version of the game you have installed matches the version expected by this modlist."; diff --git a/Wabbajack.Lib/CompilationSteps/DirectMatch.cs b/Wabbajack.Lib/CompilationSteps/DirectMatch.cs index 73cc5368..807184f4 100644 --- a/Wabbajack.Lib/CompilationSteps/DirectMatch.cs +++ b/Wabbajack.Lib/CompilationSteps/DirectMatch.cs @@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null; var result = source.EvolveTo<FromArchive>(); - var match = found.Where(f => Path.GetFileName(f.Name) == Path.GetFileName(source.Path)) + var match = found.Where(f => f.Name.FileName == source.Path.FileName) .OrderBy(f => f.NestingFactor) .FirstOrDefault() ?? found.OrderBy(f => f.NestingFactor).FirstOrDefault(); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs index 7cb8f350..7c049cab 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs @@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps var alwaysEnabled = _mo2Compiler.ModInis.Where(f => IsAlwaysEnabled(f.Value)).Select(f => f.Key).Distinct(); _allEnabledMods = _mo2Compiler.SelectedProfiles - .SelectMany(p => File.ReadAllLines(Path.Combine(_mo2Compiler.MO2Folder, "profiles", p, "modlist.txt"))) + .SelectMany(p => _mo2Compiler.MO2Folder.Combine("profiles", p, "modlist.txt").ReadAllLines()) .Where(line => line.StartsWith("+") || line.EndsWith("_separator")) .Select(line => line.Substring(1)) .Concat(alwaysEnabled) diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledVortexMods.cs b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledVortexMods.cs index a2c0ad8d..4694905a 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledVortexMods.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledVortexMods.cs @@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps var b = false; _vortexCompiler.ActiveArchives.Do(a => { - if (source.Path.Contains(a)) b = true; + if (((string)source.Path).Contains(a)) b = true; }); if (b) return null; var r = source.EvolveTo<IgnoredDirectly>(); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs b/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs index 4b0b6582..adec2b87 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs @@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!source.Path.EndsWith(_postfix)) return null; + if (!((string)source.Path).EndsWith(_postfix)) return null; var result = source.EvolveTo<IgnoredDirectly>(); result.Reason = _reason; return result; diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs b/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs index 5911b24f..30889a77 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs @@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!source.Path.StartsWith(_startDir)) return null; + if (!((string)source.Path).StartsWith(_startDir)) return null; var i = source.EvolveTo<IgnoredDirectly>(); i.Reason = "Default game file"; return i; diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs index c2dc1cdb..420a17a2 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs @@ -7,11 +7,11 @@ namespace Wabbajack.Lib.CompilationSteps public class IgnoreGameFilesIfGameFolderFilesExist : ACompilationStep { private readonly bool _gameFolderFilesExists; - private readonly string _gameFolder; + private readonly AbsolutePath _gameFolder; public IgnoreGameFilesIfGameFolderFilesExist(ACompiler compiler) : base(compiler) { - _gameFolderFilesExists = Directory.Exists(Path.Combine(((MO2Compiler)compiler).MO2Folder, Consts.GameFolderFilesDir)); + _gameFolderFilesExists = ((MO2Compiler)compiler).MO2Folder.Combine(Consts.GameFolderFilesDir).IsDirectory; _gameFolder = compiler.GamePath; } @@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps { if (_gameFolderFilesExists) { - if (source.AbsolutePath.IsInPath(_gameFolder)) + if (source.AbsolutePath.InFolder(_gameFolder)) { var result = source.EvolveTo<IgnoredDirectly>(); result.Reason = $"Ignoring game files because {Consts.GameFolderFilesDir} exists"; diff --git a/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs b/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs index feef6d93..25094e16 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs @@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!source.Path.Contains(_pattern)) return null; + if (!((string)source.Path).Contains(_pattern)) return null; var result = source.EvolveTo<IgnoredDirectly>(); result.Reason = _reason; return result; diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs b/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs index bb00fd6b..7c004d2a 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs @@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!_regex.IsMatch(source.Path)) return null; + if (!_regex.IsMatch((string)source.Path)) return null; var result = source.EvolveTo<IgnoredDirectly>(); result.Reason = _reason; return result; diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs b/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs index b8ce4b99..ce345a53 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs @@ -16,14 +16,15 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (source.Path.StartsWith(_prefix)) + if (!((string)source.Path).StartsWith(_prefix)) { - var result = source.EvolveTo<IgnoredDirectly>(); - result.Reason = _reason; - return result; + return null; } - return null; + var result = source.EvolveTo<IgnoredDirectly>(); + result.Reason = _reason; + return result; + } public override IState GetState() diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreVortex.cs b/Wabbajack.Lib/CompilationSteps/IgnoreVortex.cs index e468d158..b3282001 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreVortex.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreVortex.cs @@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (Path.GetDirectoryName(source.AbsolutePath) != _vortex.DownloadsFolder) return null; + if (source.AbsolutePath.Parent != _vortex.DownloadsFolder) return null; var result = source.EvolveTo<IgnoredDirectly>(); result.Reason = "Ignored because it is a Vortex file"; return result; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeAll.cs b/Wabbajack.Lib/CompilationSteps/IncludeAll.cs index a307aed6..9f836158 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeAll.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeAll.cs @@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { var inline = source.EvolveTo<InlineFile>(); - inline.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return inline; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs b/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs index 31539ffe..10ae1e26 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs @@ -13,9 +13,9 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!Consts.ConfigFileExtensions.Contains(Path.GetExtension(source.Path))) return null; + if (!Consts.ConfigFileExtensions.Contains(source.Path.Extension)) return null; var result = source.EvolveTo<InlineFile>(); - result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + result.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return result; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs b/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs index e416f772..8a420488 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Alphaleonis.Win32.Filesystem; using Newtonsoft.Json; +using Wabbajack.Common; namespace Wabbajack.Lib.CompilationSteps { @@ -12,19 +13,16 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (Path.GetExtension(source.AbsolutePath) != ".esp" && - Path.GetExtension(source.AbsolutePath) != ".esm") return null; + if (source.AbsolutePath.Extension != Consts.ESP && + source.AbsolutePath.Extension != Consts.ESM) return null; - var bsa = Path.Combine(Path.GetDirectoryName(source.AbsolutePath), - Path.GetFileNameWithoutExtension(source.AbsolutePath) + ".bsa"); - var bsaTextures = Path.Combine(Path.GetDirectoryName(source.AbsolutePath), - Path.GetFileNameWithoutExtension(source.AbsolutePath) + " - Textures.bsa"); - var espSize = new FileInfo(source.AbsolutePath).Length; + var bsa = source.AbsolutePath.ReplaceExtension(Consts.BSA); + var bsaTextures = source.AbsolutePath.AppendToName(bsa, " - Textures"); - if (espSize > 250 || !File.Exists(bsa) && !File.Exists(bsaTextures)) return null; + if (source.AbsolutePath.Size > 250 || !bsa.IsFile && !bsaTextures.IsFile) return null; var inline = source.EvolveTo<InlineFile>(); - inline.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return inline; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs b/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs index d12ad53d..7bc05446 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs @@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps { if (!source.Path.StartsWith(_prefix)) return null; var result = source.EvolveTo<InlineFile>(); - result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + result.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return result; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs b/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs index 01f4499a..c5d3614a 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Alphaleonis.Win32.Filesystem; using Newtonsoft.Json; +using Wabbajack.Common; namespace Wabbajack.Lib.CompilationSteps { @@ -12,9 +13,9 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!source.Path.StartsWith("mods\\") || !source.Path.EndsWith("\\meta.ini")) return null; + if (!source.Path.StartsWith("mods\\") || source.Path.FileName != Consts.MetaIni) return null; var e = source.EvolveTo<InlineFile>(); - e.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + e.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return e; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs b/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs index 63ca4457..034050f1 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Alphaleonis.Win32.Filesystem; using Newtonsoft.Json; +using Wabbajack.Common; namespace Wabbajack.Lib.CompilationSteps { @@ -15,16 +16,16 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - var files = new HashSet<string> + var files = new HashSet<AbsolutePath> { _compiler.ModListImage, _compiler.ModListReadme }; if (!files.Any(f => source.AbsolutePath.Equals(f))) return null; - if (!File.Exists(source.AbsolutePath)) return null; + if (!source.AbsolutePath.Exists) return null; var isBanner = source.AbsolutePath == _compiler.ModListImage; //var isReadme = source.AbsolutePath == ModListReadme; var result = source.EvolveTo<PropertyFile>(); - result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + result.SourceDataID = await _compiler.IncludeFile(source.AbsolutePath.ReadAllBytesAsync()); if (isBanner) { result.Type = PropertyType.Banner; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs b/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs index c7c4cfa7..a7482285 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs @@ -18,10 +18,10 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - if (!_regex.IsMatch(source.Path)) return null; + if (!_regex.IsMatch((string)source.Path)) return null; var result = source.EvolveTo<InlineFile>(); - result.SourceDataID = _compiler.IncludeFile(File.ReadAllBytes(source.AbsolutePath)); + result.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); return result; } diff --git a/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs b/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs index 6fc7363e..4e28bff5 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs @@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - return Consts.ConfigFileExtensions.Contains(Path.GetExtension(source.Path)) ? RemapFile(source) : null; + return Consts.ConfigFileExtensions.Contains(source.Path.Extension) ? await RemapFile(source) : null; } public override IState GetState() @@ -25,23 +25,23 @@ namespace Wabbajack.Lib.CompilationSteps return new State(); } - private Directive RemapFile(RawSourceFile source) + private async Task<Directive> RemapFile(RawSourceFile source) { - var data = File.ReadAllText(source.AbsolutePath); + var data = await source.AbsolutePath.ReadAllTextAsync(); var originalData = data; - data = data.Replace(_mo2Compiler.GamePath, Consts.GAME_PATH_MAGIC_BACK); - data = data.Replace(_mo2Compiler.GamePath.Replace("\\", "\\\\"), Consts.GAME_PATH_MAGIC_DOUBLE_BACK); - data = data.Replace(_mo2Compiler.GamePath.Replace("\\", "/"), Consts.GAME_PATH_MAGIC_FORWARD); + data = data.Replace((string)_mo2Compiler.GamePath, Consts.GAME_PATH_MAGIC_BACK); + data = data.Replace(((string)_mo2Compiler.GamePath).Replace("\\", "\\\\"), Consts.GAME_PATH_MAGIC_DOUBLE_BACK); + data = data.Replace(((string)_mo2Compiler.GamePath).Replace("\\", "/"), Consts.GAME_PATH_MAGIC_FORWARD); - data = data.Replace(_mo2Compiler.MO2Folder, Consts.MO2_PATH_MAGIC_BACK); - data = data.Replace(_mo2Compiler.MO2Folder.Replace("\\", "\\\\"), Consts.MO2_PATH_MAGIC_DOUBLE_BACK); - data = data.Replace(_mo2Compiler.MO2Folder.Replace("\\", "/"), Consts.MO2_PATH_MAGIC_FORWARD); + data = data.Replace((string)_mo2Compiler.MO2Folder, Consts.MO2_PATH_MAGIC_BACK); + data = data.Replace(((string)_mo2Compiler.MO2Folder).Replace("\\", "\\\\"), Consts.MO2_PATH_MAGIC_DOUBLE_BACK); + data = data.Replace(((string)_mo2Compiler.MO2Folder).Replace("\\", "/"), Consts.MO2_PATH_MAGIC_FORWARD); - data = data.Replace(_mo2Compiler.MO2DownloadsFolder, Consts.DOWNLOAD_PATH_MAGIC_BACK); - data = data.Replace(_mo2Compiler.MO2DownloadsFolder.Replace("\\", "\\\\"), + data = data.Replace((string)_mo2Compiler.MO2DownloadsFolder, Consts.DOWNLOAD_PATH_MAGIC_BACK); + data = data.Replace(((string)_mo2Compiler.MO2DownloadsFolder).Replace("\\", "\\\\"), Consts.DOWNLOAD_PATH_MAGIC_DOUBLE_BACK); - data = data.Replace(_mo2Compiler.MO2DownloadsFolder.Replace("\\", "/"), Consts.DOWNLOAD_PATH_MAGIC_FORWARD); + data = data.Replace(((string)_mo2Compiler.MO2DownloadsFolder).Replace("\\", "/"), Consts.DOWNLOAD_PATH_MAGIC_FORWARD); if (data == originalData) return null; diff --git a/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs b/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs index 81dd5b69..e71dfc0c 100644 --- a/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs +++ b/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs @@ -18,10 +18,10 @@ namespace Wabbajack.Lib.CompilationSteps public override async ValueTask<Directive> Run(RawSourceFile source) { - var filename = Path.GetFileName(source.Path); - var gameFile = Path.Combine(_mo2Compiler.GamePath, "Data", filename); + var filename = source.Path.FileName; + var gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename); if (!Consts.GameESMs.Contains(filename) || !source.Path.StartsWith("mods\\") || - !File.Exists(gameFile)) return null; + !gameFile.Exists) return null; Utils.Log( $"An ESM named {filename} was found in a mod that shares a name with one of the core game ESMs, it is assumed this is a cleaned ESM and it will be binary patched"); @@ -31,9 +31,9 @@ namespace Wabbajack.Lib.CompilationSteps Utils.Status($"Generating patch of {filename}"); await using (var ms = new MemoryStream()) { - await Utils.CreatePatch(File.ReadAllBytes(gameFile), File.ReadAllBytes(source.AbsolutePath), ms); + await Utils.CreatePatch(await gameFile.ReadAllBytesAsync(), await source.AbsolutePath.ReadAllBytesAsync(), ms); var data = ms.ToArray(); - result.SourceDataID = _compiler.IncludeFile(data); + result.SourceDataID = await _compiler.IncludeFile(data); Utils.Log($"Generated a {data.Length} byte patch for {filename}"); } diff --git a/Wabbajack.Lib/Data.cs b/Wabbajack.Lib/Data.cs index 96449de4..1ceb4ab7 100644 --- a/Wabbajack.Lib/Data.cs +++ b/Wabbajack.Lib/Data.cs @@ -12,11 +12,9 @@ namespace Wabbajack.Lib { public class RawSourceFile { - // ToDo - // Make readonly - public string Path; + public readonly RelativePath Path; - public RawSourceFile(VirtualFile file, string path) + public RawSourceFile(VirtualFile file, RelativePath path) { File = file; Path = path; @@ -153,7 +151,7 @@ namespace Wabbajack.Lib /// location the file will be copied to, relative to the install path. /// </summary> [Key(2)] - public string To { get; set; } + public RelativePath To { get; set; } } public class IgnoredDirectly : Directive @@ -172,14 +170,14 @@ namespace Wabbajack.Lib /// Data that will be written as-is to the destination location; /// </summary> [Key(3)] - public string SourceDataID; + public RelativePath SourceDataID { get; set; } } [MessagePackObject] public class ArchiveMeta : Directive { [Key(3)] - public string SourceDataID { get; set; } + public RelativePath SourceDataID { get; set; } } public enum PropertyType { Banner, Readme } @@ -218,7 +216,7 @@ namespace Wabbajack.Lib private string _fullPath; [Key(3)] - public string[] ArchiveHashPath { get; set; } + public HashRelativePath ArchiveHashPath { get; set; } [IgnoreMember] public VirtualFile FromFile { get; set; } @@ -257,7 +255,7 @@ namespace Wabbajack.Lib [Key(0)] public Hash Hash { get; set; } [Key(1)] - public string RelativePath { get; set; } + public RelativePath RelativePath { get; set; } } [MessagePackObject] diff --git a/Wabbajack.Lib/MO2Compiler.cs b/Wabbajack.Lib/MO2Compiler.cs index 3483f3d8..8c07eede 100644 --- a/Wabbajack.Lib/MO2Compiler.cs +++ b/Wabbajack.Lib/MO2Compiler.cs @@ -26,41 +26,41 @@ namespace Wabbajack.Lib public class MO2Compiler : ACompiler { - private string _mo2DownloadsFolder; + private AbsolutePath _mo2DownloadsFolder; - public string MO2Folder; + public AbsolutePath MO2Folder; public string MO2Profile { get; } public Dictionary<string, dynamic> ModMetas { get; set; } public override ModManager ModManager => ModManager.MO2; - public override string GamePath { get; } + public override AbsolutePath GamePath { get; } public GameMetaData CompilingGame { get; set; } - public override string ModListOutputFolder => "output_folder"; + public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint(); - public override string ModListOutputFile { get; } + public override AbsolutePath ModListOutputFile { get; } - public override string VFSCacheName => Path.Combine( - Consts.LocalAppDataPath, - $"vfs_compile_cache-{Path.Combine(MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin"); + public override AbsolutePath VFSCacheName => + Consts.LocalAppDataPath.Combine( + $"vfs_compile_cache-{Path.Combine((string)MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin"); - public MO2Compiler(string mo2Folder, string mo2Profile, string outputFile) + public MO2Compiler(AbsolutePath mo2Folder, string mo2Profile, AbsolutePath outputFile) { MO2Folder = mo2Folder; MO2Profile = mo2Profile; - MO2Ini = Path.Combine(MO2Folder, "ModOrganizer.ini").LoadIniFile(); + MO2Ini = MO2Folder.Combine("ModOrganizer.ini").LoadIniFile(); var mo2game = (string)MO2Ini.General.gameName; CompilingGame = GameRegistry.Games.First(g => g.Value.MO2Name == mo2game).Value; - GamePath = ((string)MO2Ini.General.gamePath).Replace("\\\\", "\\"); + GamePath = new AbsolutePath((string)MO2Ini.General.gamePath.Replace("\\\\", "\\")); ModListOutputFile = outputFile; } public dynamic MO2Ini { get; } - public string MO2DownloadsFolder + public AbsolutePath MO2DownloadsFolder { get { @@ -74,9 +74,9 @@ namespace Wabbajack.Lib set => _mo2DownloadsFolder = value; } - public static string GetTypicalDownloadsFolder(string mo2Folder) => Path.Combine(mo2Folder, "downloads"); + public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads"); - public string MO2ProfileDir => Path.Combine(MO2Folder, "profiles", MO2Profile); + public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile); internal UserStatus User { get; private set; } public ConcurrentBag<Directive> ExtraFiles { get; private set; } @@ -91,9 +91,9 @@ namespace Wabbajack.Lib UpdateTracker.Reset(); UpdateTracker.NextStep("Gathering information"); Info("Looking for other profiles"); - var otherProfilesPath = Path.Combine(MO2ProfileDir, "otherprofiles.txt"); + var otherProfilesPath = MO2ProfileDir.Combine("otherprofiles.txt"); SelectedProfiles = new HashSet<string>(); - if (File.Exists(otherProfilesPath)) SelectedProfiles = File.ReadAllLines(otherProfilesPath).ToHashSet(); + if (otherProfilesPath.Exists) SelectedProfiles = (await otherProfilesPath.ReadAllLinesAsync()).ToHashSet(); SelectedProfiles.Add(MO2Profile); Info("Using Profiles: " + string.Join(", ", SelectedProfiles.OrderBy(p => p))); diff --git a/Wabbajack.Lib/VortexCompiler.cs b/Wabbajack.Lib/VortexCompiler.cs index fa11b0c4..4c375120 100644 --- a/Wabbajack.Lib/VortexCompiler.cs +++ b/Wabbajack.Lib/VortexCompiler.cs @@ -31,14 +31,14 @@ namespace Wabbajack.Lib public Game Game { get; } public string GameName { get; } - public string VortexFolder { get; set; } - public string StagingFolder { get; set; } - public string DownloadsFolder { get; set; } + public AbsolutePath VortexFolder { get; set; } + public AbsolutePath StagingFolder { get; set; } + public AbsolutePath DownloadsFolder { get; set; } public override ModManager ModManager => ModManager.Vortex; - public override string GamePath { get; } - public override string ModListOutputFolder => "output_folder"; - public override string ModListOutputFile { get; } + public override AbsolutePath GamePath { get; } + public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint(); + public override AbsolutePath ModListOutputFile { get; } public const string StagingMarkerName = "__vortex_staging_folder"; public const string DownloadMarkerName = "__vortex_downloads_folder"; diff --git a/Wabbajack.VirtualFileSystem/Context.cs b/Wabbajack.VirtualFileSystem/Context.cs index fadc1c52..6da9785f 100644 --- a/Wabbajack.VirtualFileSystem/Context.cs +++ b/Wabbajack.VirtualFileSystem/Context.cs @@ -233,9 +233,12 @@ namespace Wabbajack.VirtualFileSystem #region KnownFiles private List<HashRelativePath> _knownFiles = new List<HashRelativePath>(); - public void AddKnown(IEnumerable<HashRelativePath> known) + private Dictionary<Hash, AbsolutePath> _knownArchives = new Dictionary<Hash, AbsolutePath>(); + public void AddKnown(IEnumerable<HashRelativePath> known, Dictionary<Hash, AbsolutePath> archives) { _knownFiles.AddRange(known); + foreach (var (key, value) in archives) + _knownArchives.TryAdd(key, value); } public async Task BackfillMissing()