wabbajack/Wabbajack.Lib/MO2Compiler.cs

591 lines
25 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
{
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();
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;
2020-02-15 13:12:06 +00:00
await VFS.IntegrateFromFile(VFSCacheName);
2020-03-28 02:54:14 +00:00
var roots = new List<AbsolutePath>
{
MO2Folder, GamePath, MO2DownloadsFolder, CompilingGame.GameLocation()
};
// 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,
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
}
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(
2020-03-22 15:50:53 +00:00
$"http://build.wabbajack.org/indexed_files/{vf.Hash.ToHex()}/meta.ini");
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 = srcFile.OpenRead();
await using var outputStream = IncludeFile(out var id);
entry.PatchID = id;
await using var destStream = await LoadDataForTo(entry.To, absolutePaths);
2020-04-04 04:02:53 +00:00
await Utils.CreatePatch(srcStream, srcFile.Hash, destStream, entry.Hash, outputStream);
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))
2020-03-25 23:33:52 +00:00
return 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 = 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();
file.CopyDataTo(returnStream);
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 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
};
}
}
}