wabbajack/Wabbajack.Lib/MO2Compiler.cs

612 lines
26 KiB
C#
Raw Normal View History

using Compression.BSA;
2019-10-07 17:33:34 +00:00
using System;
2019-07-26 20:59:14 +00:00
using System.Collections.Concurrent;
2019-07-21 04:40:54 +00:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
2019-07-21 04:40:54 +00:00
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.FileUploader;
using Wabbajack.Lib.Validation;
using Wabbajack.VirtualFileSystem;
using Path = Alphaleonis.Win32.Filesystem.Path;
2019-07-21 04:40:54 +00:00
namespace Wabbajack.Lib
2019-07-21 04:40:54 +00:00
{
public class MO2Compiler : ACompiler
2019-07-21 04:40:54 +00:00
{
2020-03-25 12:47:25 +00:00
private AbsolutePath _mo2DownloadsFolder;
2019-11-15 13:06:34 +00:00
2020-03-25 12:47:25 +00:00
public AbsolutePath MO2Folder;
2019-07-21 04:40:54 +00:00
2020-03-25 23:15:19 +00:00
public AbsolutePath MO2ModsFolder => MO2Folder.Combine(Consts.MO2ModFolderName);
public string MO2Profile { get; }
public override ModManager ModManager => ModManager.MO2;
2020-03-25 12:47:25 +00:00
public override AbsolutePath GamePath { get; }
2020-04-09 21:05:07 +00:00
public GameMetaData CompilingGame { get; }
2020-03-25 12:47:25 +00:00
public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint();
2020-03-25 12:47:25 +00:00
public override AbsolutePath ModListOutputFile { get; }
2020-03-25 12:47:25 +00:00
public override AbsolutePath VFSCacheName =>
Consts.LocalAppDataPath.Combine(
$"vfs_compile_cache-2-{Path.Combine((string)MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin");
2020-04-09 21:05:07 +00:00
public dynamic MO2Ini { get; }
public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads");
public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile);
public ConcurrentBag<Directive> ExtraFiles { get; private set; } = new ConcurrentBag<Directive>();
2020-04-09 21:05:07 +00:00
public Dictionary<AbsolutePath, dynamic> ModInis { get; } = new Dictionary<AbsolutePath, dynamic>();
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
2020-03-25 12:47:25 +00:00
public MO2Compiler(AbsolutePath mo2Folder, string mo2Profile, AbsolutePath outputFile)
: base(steps: 20)
{
2019-11-21 15:51:57 +00:00
MO2Folder = mo2Folder;
MO2Profile = mo2Profile;
2020-03-25 12:47:25 +00:00
MO2Ini = MO2Folder.Combine("ModOrganizer.ini").LoadIniFile();
var mo2game = (string)MO2Ini.General.gameName;
CompilingGame = GameRegistry.Games.First(g => g.Value.MO2Name == mo2game).Value;
2020-03-25 12:47:25 +00:00
GamePath = new AbsolutePath((string)MO2Ini.General.gamePath.Replace("\\\\", "\\"));
ModListOutputFile = outputFile;
}
2020-03-25 12:47:25 +00:00
public AbsolutePath MO2DownloadsFolder
2019-07-22 22:17:46 +00:00
{
2019-07-21 04:40:54 +00:00
get
{
2020-03-28 02:54:14 +00:00
if (_mo2DownloadsFolder != default) return _mo2DownloadsFolder;
if (MO2Ini != null)
if (MO2Ini.Settings != null)
if (MO2Ini.Settings.download_directory != null)
return MO2Ini.Settings.download_directory.Replace("/", "\\");
return GetTypicalDownloadsFolder(MO2Folder);
2019-07-21 04:40:54 +00:00
}
set => _mo2DownloadsFolder = value;
2019-07-21 04:40:54 +00:00
}
protected override async Task<bool> _Begin(CancellationToken cancel)
2019-07-21 04:40:54 +00:00
{
2020-05-13 12:16:14 +00:00
var _ = Metrics.Send("begin_compiling", MO2Profile ?? "unknown");
if (cancel.IsCancellationRequested) return false;
Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize()));
2019-11-17 04:16:42 +00:00
UpdateTracker.Reset();
UpdateTracker.NextStep("Gathering information");
Info("Looking for other profiles");
2020-03-25 12:47:25 +00:00
var otherProfilesPath = MO2ProfileDir.Combine("otherprofiles.txt");
SelectedProfiles = new HashSet<string>();
2020-03-25 12:47:25 +00:00
if (otherProfilesPath.Exists) SelectedProfiles = (await otherProfilesPath.ReadAllLinesAsync()).ToHashSet();
2020-05-13 14:06:18 +00:00
SelectedProfiles.Add(MO2Profile!);
Info("Using Profiles: " + string.Join(", ", SelectedProfiles.OrderBy(p => p)));
2020-02-15 13:12:06 +00:00
Utils.Log($"VFS File Location: {VFSCacheName}");
if (cancel.IsCancellationRequested) return false;
if (VFSCacheName.Exists)
await VFS.IntegrateFromFile(VFSCacheName);
List<AbsolutePath> roots;
if (UseGamePaths)
{
roots = new List<AbsolutePath>
{
MO2Folder, GamePath, MO2DownloadsFolder, CompilingGame.GameLocation()
};
}
else
{
roots = new List<AbsolutePath>
{
MO2Folder, MO2DownloadsFolder
};
}
// TODO: make this generic so we can add more paths
2020-03-25 22:49:32 +00:00
var lootPath = (AbsolutePath)Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"LOOT");
IEnumerable<RawSourceFile> lootFiles = new List<RawSourceFile>();
2020-03-25 22:49:32 +00:00
if (lootPath.Exists)
{
2020-03-25 22:49:32 +00:00
roots.Add((AbsolutePath)lootPath);
}
UpdateTracker.NextStep("Indexing folders");
if (cancel.IsCancellationRequested) return false;
await VFS.AddRoots(roots);
2020-02-15 13:12:06 +00:00
await VFS.WriteToFile(VFSCacheName);
2020-03-25 22:49:32 +00:00
if (lootPath.Exists)
{
2020-04-09 21:05:07 +00:00
if (CompilingGame.MO2Name == null)
{
throw new ArgumentException("Compiling game had no MO2 name specified.");
}
var lootGameDirs = new []
{
CompilingGame.MO2Name, // most of the games use the MO2 name
CompilingGame.MO2Name.Replace(" ", "") //eg: Fallout 4 -> Fallout4
};
2020-04-03 22:56:14 +00:00
var lootGameDir = lootGameDirs.Select(x => lootPath.Combine(x))
.FirstOrDefault(p => p.IsDirectory);
2020-04-03 22:56:14 +00:00
if (lootGameDir != default)
{
Utils.Log($"Found LOOT game folder at {lootGameDir}");
2020-04-03 22:56:14 +00:00
lootFiles = lootGameDir.EnumerateFiles(false)
.Where(p => p.FileName == (RelativePath)"userlist.yaml")
.Where(p => p.IsFile)
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p],
2020-04-03 22:56:14 +00:00
Consts.LOOTFolderFilesDir.Combine(p.RelativeTo(lootPath))));
if (!lootFiles.Any())
Utils.Log(
$"Found no LOOT user data for {CompilingGame.HumanFriendlyGameName} at {lootGameDir}!");
}
}
if (cancel.IsCancellationRequested) return false;
2019-11-17 04:16:42 +00:00
UpdateTracker.NextStep("Cleaning output folder");
2020-03-28 04:33:26 +00:00
await ModListOutputFolder.DeleteDirectory();
if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Inferring metas for game file downloads");
await InferMetas();
if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Reindexing downloads after meta inferring");
await VFS.AddRoot(MO2DownloadsFolder);
await VFS.WriteToFile(VFSCacheName);
2019-09-24 04:34:21 +00:00
if (cancel.IsCancellationRequested) return false;
UpdateTracker.NextStep("Pre-validating Archives");
2019-09-24 04:34:21 +00:00
// Find all Downloads
2020-03-25 22:49:32 +00:00
IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles()
.Where(f => f.WithExtension(Consts.MetaFileExtension).Exists)
2020-04-10 01:29:53 +00:00
.PMap(Queue, async f => new IndexedArchive(VFS.Index.ByRootPath[f])
{
2020-03-25 22:49:32 +00:00
Name = (string)f.FileName,
IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(),
Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync()
})).ToList();
var stockGameFolder = CompilingGame.GameLocation();
foreach (var (relativePath, hash) in await ClientAPI.GetGameFiles(CompilingGame.Game, Version.Parse(CompilingGame.InstalledVersion)))
{
if (!VFS.Index.ByRootPath.TryGetValue(relativePath.RelativeTo(stockGameFolder), out var virtualFile))
continue;
if (virtualFile.Hash != hash)
{
Utils.Log(
$"File {relativePath} int the game folder appears to be modified, it will not be used during compilation");
continue;
}
var state = new GameFileSourceDownloader.State
{
Game = CompilingGame.Game, GameVersion = CompilingGame.InstalledVersion, GameFile = relativePath
};
Utils.Log($"Adding Game file: {relativePath}");
IndexedArchives.Add(new IndexedArchive(virtualFile)
{
Name = (string)relativePath.FileName,
IniData = state.GetMetaIniString().LoadIniString(),
Meta = state.GetMetaIniString()
});
}
2019-12-13 13:02:58 +00:00
await CleanInvalidArchives();
UpdateTracker.NextStep("Finding Install Files");
2020-03-25 22:49:32 +00:00
ModListOutputFolder.CreateDirectory();
2020-03-25 22:49:32 +00:00
var mo2Files = MO2Folder.EnumerateFiles()
.Where(p => p.IsFile)
.Select(p =>
{
2020-03-25 22:49:32 +00:00
if (!VFS.Index.ByRootPath.ContainsKey(p))
Utils.Log($"WELL THERE'S YOUR PROBLEM: {p} {VFS.Index.ByRootPath.Count}");
return new RawSourceFile(VFS.Index.ByRootPath[p], p.RelativeTo(MO2Folder));
});
// If Game Folder Files exists, ignore the game folder
IEnumerable<RawSourceFile> gameFiles;
2020-03-25 22:49:32 +00:00
if (!MO2Folder.Combine(Consts.GameFolderFilesDir).Exists)
{
2020-03-25 22:49:32 +00:00
gameFiles = GamePath.EnumerateFiles()
.Where(p => p.IsFile)
.Where(p => p.Extension!= Consts.HashFileExtension)
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p],
2020-03-25 22:49:32 +00:00
Consts.GameFolderFilesDir.Combine(p.RelativeTo(GamePath))));
}
else
{
gameFiles = new List<RawSourceFile>();
}
2019-11-15 13:06:34 +00:00
IndexedFiles = IndexedArchives.SelectMany(f => f.File.ThisAndAllChildren)
.OrderBy(f => f.NestingFactor)
.GroupBy(f => f.Hash)
.ToDictionary(f => f.Key, f => f.AsEnumerable());
2020-04-09 21:05:07 +00:00
AllFiles.SetTo(mo2Files.Concat(gameFiles)
2019-11-21 15:51:57 +00:00
.Concat(lootFiles)
2020-04-09 21:05:07 +00:00
.DistinctBy(f => f.Path));
2019-09-26 22:32:15 +00:00
Info($"Found {AllFiles.Count} files to build into mod list");
2019-07-21 04:40:54 +00:00
if (cancel.IsCancellationRequested) return false;
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Verifying destinations");
var dups = AllFiles.GroupBy(f => f.Path)
.Where(fs => fs.Count() > 1)
.Select(fs =>
{
Utils.Log($"Duplicate files installed to {fs.Key} from : {String.Join(", ", fs.Select(f => f.AbsolutePath))}");
return fs;
}).ToList();
if (dups.Count > 0)
{
Error($"Found {dups.Count} duplicates, exiting");
}
if (cancel.IsCancellationRequested) return false;
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Loading INIs");
2020-04-09 21:05:07 +00:00
ModInis.SetTo(MO2Folder.Combine(Consts.MO2ModFolderName)
2020-03-25 23:15:19 +00:00
.EnumerateDirectories()
.Select(f =>
{
2020-03-25 23:15:19 +00:00
var modName = f.FileName;
var metaPath = f.Combine("meta.ini");
2020-03-28 18:22:53 +00:00
return metaPath.Exists ? (mod_name: f, metaPath.LoadIniFile()) : default;
})
2020-03-28 18:22:53 +00:00
.Where(f => f.Item1 != default)
2020-04-09 21:05:07 +00:00
.Select(f => new KeyValuePair<AbsolutePath, dynamic>(f.Item1, f.Item2)));
if (cancel.IsCancellationRequested) return false;
var stack = MakeStack();
2019-11-17 04:16:42 +00:00
UpdateTracker.NextStep("Running Compilation Stack");
var results = await AllFiles.PMap(Queue, UpdateTracker, f => RunStack(stack, f));
2019-07-21 04:40:54 +00:00
2019-07-26 20:59:14 +00:00
// Add the extra files that were generated by the stack
if (cancel.IsCancellationRequested) return false;
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep($"Adding {ExtraFiles.Count} that were generated by the stack");
results = results.Concat(ExtraFiles).ToArray();
2019-07-26 20:59:14 +00:00
var noMatch = results.OfType<NoMatch>().ToArray();
PrintNoMatches(noMatch);
if (CheckForNoMatchExit(noMatch)) return false;
2019-07-21 04:40:54 +00:00
2020-04-09 21:05:07 +00:00
InstallDirectives.SetTo(results.Where(i => !(i is IgnoredDirectly)));
2019-07-21 22:47:17 +00:00
2019-09-24 15:26:44 +00:00
Info("Getting Nexus api_key, please click authorize if a browser window appears");
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Verifying Files");
2019-09-23 21:37:10 +00:00
zEditIntegration.VerifyMerges(this);
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Gathering Archives");
await GatherArchives();
// Don't await this because we don't care if it fails.
Utils.Log("Finding States to package");
2020-03-31 22:05:36 +00:00
await AuthorAPI.UploadPackagedInis(SelectedArchives.ToArray());
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Including Archive Metadata");
await IncludeArchiveMetadata();
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Building Patches");
await BuildPatches();
2019-07-23 04:27:26 +00:00
UpdateTracker.NextStep("Gathering Metadata");
await GatherMetaData();
ModList = new ModList
2019-07-23 04:27:26 +00:00
{
GameType = CompilingGame.Game,
2019-10-23 17:00:45 +00:00
WabbajackVersion = WabbajackVersion,
2019-12-03 21:13:29 +00:00
Archives = SelectedArchives.ToList(),
ModManager = ModManager.MO2,
2019-08-02 23:04:04 +00:00
Directives = InstallDirectives,
2020-05-13 14:06:18 +00:00
Name = ModListName ?? MO2Profile!,
Author = ModListAuthor ?? "",
Description = ModListDescription ?? "",
2020-04-15 17:40:41 +00:00
Readme = ModlistReadme ?? "",
2020-03-28 02:54:14 +00:00
Image = ModListImage != default ? ModListImage.FileName : default,
2020-04-27 09:58:33 +00:00
Website = !string.IsNullOrWhiteSpace(ModListWebsite) ? new Uri(ModListWebsite) : null,
Version = ModlistVersion ?? new Version(1,0,0,0),
2020-04-27 09:58:33 +00:00
IsNSFW = ModlistIsNSFW
2019-07-23 04:27:26 +00:00
};
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Running Validation");
2020-03-22 15:50:53 +00:00
await ValidateModlist.RunValidation(ModList);
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Generating Report");
2020-02-01 12:13:12 +00:00
GenerateManifest();
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Exporting Modlist");
2020-04-01 23:59:22 +00:00
await ExportModList();
2019-07-23 04:27:26 +00:00
ResetMembers();
2019-11-24 23:03:36 +00:00
UpdateTracker.NextStep("Done Building Modlist");
2019-09-24 04:20:24 +00:00
return true;
2019-07-21 04:40:54 +00:00
}
public bool UseGamePaths { get; set; } = true;
private async Task CleanInvalidArchives()
{
var remove = (await IndexedArchives.PMap(Queue, async a =>
{
try
{
await ResolveArchive(a);
return null;
}
catch
{
return a;
}
2020-04-09 21:05:07 +00:00
})).NotNull().ToHashSet();
if (remove.Count == 0)
return;
Utils.Log(
$"Removing {remove.Count} archives from the compilation state, this is probably not an issue but reference this if you have compilation failures");
2019-12-29 22:57:01 +00:00
remove.Do(r => Utils.Log($"Resolution failed for: {r.File.FullPath}"));
IndexedArchives.RemoveAll(a => remove.Contains(a));
}
private async Task InferMetas()
{
2020-03-25 23:15:19 +00:00
async Task<bool> HasInvalidMeta(AbsolutePath filename)
{
2020-03-25 23:15:19 +00:00
var metaname = filename.WithExtension(Consts.MetaFileExtension);
2020-03-28 02:54:14 +00:00
if (!metaname.Exists) return true;
2020-03-25 23:15:19 +00:00
return await DownloadDispatcher.ResolveArchive(metaname.LoadIniFile()) == null;
}
2020-03-25 23:15:19 +00:00
var to_find = (await MO2DownloadsFolder.EnumerateFiles()
.Where(f => f.Extension != Consts.MetaFileExtension && f.Extension !=Consts.HashFileExtension)
.PMap(Queue, async f => await HasInvalidMeta(f) ? f : default))
.Where(f => f.Exists)
.ToList();
if (to_find.Count == 0) return;
Utils.Log($"Attempting to infer {to_find.Count} metas from the server.");
await to_find.PMap(Queue, async f =>
{
2020-03-25 23:15:19 +00:00
var vf = VFS.Index.ByRootPath[f];
2020-02-26 04:00:28 +00:00
var client = new Common.Http.Client();
using var response =
await client.GetAsync(
$"http://build.wabbajack.org/indexed_files/{vf.Hash.ToHex()}/meta.ini", errorsAsExceptions: false);
if (!response.IsSuccessStatusCode)
{
2020-03-25 23:15:19 +00:00
await vf.AbsoluteName.WithExtension(Consts.MetaFileExtension).WriteAllLinesAsync(
"[General]",
"unknownArchive=true");
return;
}
2020-03-25 23:15:19 +00:00
var iniData = await response.Content.ReadAsStringAsync();
Utils.Log($"Inferred .meta for {vf.FullPath.FileName}, writing to disk");
await vf.AbsoluteName.WithExtension(Consts.MetaFileExtension).WriteAllTextAsync(iniData);
});
}
private async Task IncludeArchiveMetadata()
{
Utils.Log($"Including {SelectedArchives.Count} .meta files for downloads");
2020-03-25 23:33:52 +00:00
await SelectedArchives.PMap(Queue, async a =>
{
2020-03-25 23:33:52 +00:00
var source = MO2DownloadsFolder.Combine(a.Name + Consts.MetaFileExtension);
var ini = a.State.GetMetaIniString();
var (id, fullPath) = await IncludeString(ini);
2020-03-25 23:33:52 +00:00
InstallDirectives.Add(new ArchiveMeta
{
SourceDataID = id,
2020-03-25 23:33:52 +00:00
Size = source.Size,
Hash = await fullPath.FileHashAsync(),
2020-03-25 23:33:52 +00:00
To = source.FileName
});
});
}
2019-07-23 04:27:26 +00:00
/// <summary>
/// Clear references to lists that hold a lot of data.
2019-07-23 04:27:26 +00:00
/// </summary>
private void ResetMembers()
{
AllFiles = new List<RawSourceFile>();
InstallDirectives = new List<Directive>();
SelectedArchives = new List<Archive>();
ExtraFiles = new ConcurrentBag<Directive>();
2019-07-23 04:27:26 +00:00
}
2019-07-22 03:36:25 +00:00
/// <summary>
/// Fills in the Patch fields in files that require them
2019-07-22 03:36:25 +00:00
/// </summary>
private async Task BuildPatches()
2019-07-22 03:36:25 +00:00
{
Info("Gathering patch files");
2020-03-25 23:33:52 +00:00
await InstallDirectives.OfType<PatchedFromArchive>()
.Where(p => p.PatchID == null)
2020-03-25 23:33:52 +00:00
.PMap(Queue, async p =>
{
if (Utils.TryGetPatch(p.FromHash, p.Hash, out var bytes))
2020-03-25 23:33:52 +00:00
p.PatchID = await IncludeFile(bytes);
});
2019-07-22 03:36:25 +00:00
var groups = InstallDirectives.OfType<PatchedFromArchive>()
2020-03-28 02:54:14 +00:00
.Where(p => p.PatchID == default)
2020-03-25 23:33:52 +00:00
.GroupBy(p => p.ArchiveHashPath.BaseHash)
.ToList();
2019-07-22 03:36:25 +00:00
2019-09-26 22:32:15 +00:00
Info($"Patching building patches from {groups.Count} archives");
2019-11-21 15:51:57 +00:00
var absolutePaths = AllFiles.ToDictionary(e => e.Path, e => e.AbsolutePath);
await groups.PMap(Queue, group => BuildArchivePatches(group.Key, group, absolutePaths));
2019-07-22 03:36:25 +00:00
var firstFailedPatch = InstallDirectives.OfType<PatchedFromArchive>().FirstOrDefault(f => f.PatchID == null);
if (firstFailedPatch != null)
Error($"Missing patches after generation, this should not happen. First failure: {firstFailedPatch.FullPath}");
2019-07-22 03:36:25 +00:00
}
2020-03-25 23:33:52 +00:00
private async Task BuildArchivePatches(Hash archiveSha, IEnumerable<PatchedFromArchive> group,
Dictionary<RelativePath, AbsolutePath> absolutePaths)
2019-07-22 03:36:25 +00:00
{
await using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath)));
2020-04-04 04:02:53 +00:00
var byPath = files.GroupBy(f => string.Join("|", f.FilesInFullPath.Skip(1).Select(i => i.Name)))
.ToDictionary(f => f.Key, f => f.First());
// Now Create the patches
await @group.PMap(Queue, async entry =>
2019-07-22 03:36:25 +00:00
{
2020-04-04 04:02:53 +00:00
Info($"Patching {entry.To}");
Status($"Patching {entry.To}");
var srcFile = byPath[string.Join("|", entry.ArchiveHashPath.Paths)];
await using var srcStream = await srcFile.OpenRead();
await using var outputStream = await IncludeFile(out var id).Create();
2020-04-04 04:02:53 +00:00
entry.PatchID = id;
await using var destStream = await LoadDataForTo(entry.To, absolutePaths);
2020-06-02 03:41:34 +00:00
await Utils.CreatePatchCached(srcStream, srcFile.Hash, destStream, entry.Hash, outputStream);
2020-04-04 04:02:53 +00:00
Info($"Patch size {outputStream.Length} for {entry.To}");
});
2019-07-22 03:36:25 +00:00
}
private async Task<FileStream> LoadDataForTo(RelativePath to, Dictionary<RelativePath, AbsolutePath> absolutePaths)
2019-07-26 20:59:14 +00:00
{
2019-11-21 15:51:57 +00:00
if (absolutePaths.TryGetValue(to, out var absolute))
return await absolute.OpenRead();
2019-07-26 20:59:14 +00:00
if (to.StartsWith(Consts.BSACreationDir))
{
2020-03-25 23:33:52 +00:00
var bsaId = (RelativePath)((string)to).Split('\\')[1];
var bsa = InstallDirectives.OfType<CreateBSA>().First(b => b.TempID == bsaId);
await using var a = await BSADispatch.OpenRead(MO2Folder.Combine(bsa.To));
2020-03-25 23:33:52 +00:00
var find = (RelativePath)Path.Combine(((string)to).Split('\\').Skip(2).ToArray());
var file = a.Files.First(e => e.Path == find);
var returnStream = new TempStream();
await file.CopyDataTo(returnStream);
2020-03-25 23:33:52 +00:00
returnStream.Position = 0;
return returnStream;
2019-07-26 20:59:14 +00:00
}
2020-04-09 21:05:07 +00:00
throw new ArgumentException($"Couldn't load data for {to}");
2019-07-26 20:59:14 +00:00
}
public override IEnumerable<ICompilationStep> GetStack()
{
2020-03-25 23:33:52 +00:00
return MakeStack();
}
2019-07-21 04:40:54 +00:00
/// <summary>
/// Creates a execution stack. The stack should be passed into Run stack. Each function
/// in this stack will be run in-order and the first to return a non-null result will have its
/// result included into the pack
2019-07-21 04:40:54 +00:00
/// </summary>
/// <returns></returns>
public override IEnumerable<ICompilationStep> MakeStack()
2019-07-21 04:40:54 +00:00
{
Utils.Log("Generating compilation stack");
return new List<ICompilationStep>
{
2020-01-07 00:24:33 +00:00
new IgnoreGameFilesIfGameFolderFilesExist(this),
new IncludePropertyFiles(this),
new IgnoreStartsWith(this,"logs\\"),
new IgnoreStartsWith(this, "downloads\\"),
new IgnoreStartsWith(this,"webcache\\"),
new IgnoreStartsWith(this, "overwrite\\"),
2020-03-04 05:23:08 +00:00
new IgnoreStartsWith(this, "crashDumps\\"),
new IgnorePathContains(this,"temporary_logs"),
new IgnorePathContains(this, "GPUCache"),
new IgnorePathContains(this, "SSEEdit Cache"),
new IgnoreEndsWith(this, ".pyc"),
new IgnoreEndsWith(this, ".log"),
new IgnoreOtherProfiles(this),
new IgnoreDisabledMods(this),
new IncludeThisProfile(this),
2019-07-21 04:40:54 +00:00
// Ignore the ModOrganizer.ini file it contains info created by MO2 on startup
new IncludeStubbedConfigFiles(this),
new IncludeLootFiles(this),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Data")),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Papyrus Compiler")),
new IgnoreStartsWith(this, Path.Combine((string)Consts.GameFolderFilesDir, "Skyrim")),
new IgnoreRegex(this, Consts.GameFolderFilesDir + "\\\\.*\\.bsa"),
new IncludeRegex(this, "^[^\\\\]*\\.bat$"),
new IncludeModIniData(this),
new DirectMatch(this),
new IncludeTaggedMods(this, Consts.WABBAJACK_INCLUDE),
new DeconstructBSAs(this), // Deconstruct BSAs before building patches so we don't generate massive patch files
new IncludePatches(this),
new IncludeDummyESPs(this),
2019-07-21 12:42:29 +00:00
2019-07-26 20:59:14 +00:00
2019-07-21 12:42:29 +00:00
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
new IgnoreGameFiles(this),
2019-07-26 20:59:14 +00:00
2019-09-26 23:08:10 +00:00
// There are some types of files that will error the compilation, because they're created on-the-fly via tools
2019-07-26 20:59:14 +00:00
// so if we don't have a match by this point, just drop them.
new IgnoreEndsWith(this, ".ini"),
new IgnoreEndsWith(this, ".html"),
new IgnoreEndsWith(this, ".txt"),
2019-07-26 20:59:14 +00:00
// Don't know why, but this seems to get copied around a bit
new IgnoreEndsWith(this, "HavokBehaviorPostProcess.exe"),
2019-08-03 17:37:32 +00:00
// Theme file MO2 downloads somehow
new IgnoreEndsWith(this, "splash.png"),
// File to force MO2 into portable mode
new IgnoreEndsWith(this, "portable.txt"),
new IgnoreEndsWith(this, ".bin"),
new IgnoreEndsWith(this, ".refcache"),
2019-09-02 22:36:57 +00:00
new IgnoreWabbajackInstallCruft(this),
2019-07-21 22:47:17 +00:00
new PatchStockESMs(this),
new IncludeAllConfigs(this),
new zEditIntegration.IncludeZEditPatches(this),
2019-11-02 18:36:38 +00:00
new IncludeTaggedMods(this, Consts.WABBAJACK_NOMATCH_INCLUDE),
new DropAll(this)
2019-07-21 04:40:54 +00:00
};
}
}
}