2020-04-01 23:59:22 +00:00
|
|
|
using System;
|
2019-11-03 15:26:51 +00:00
|
|
|
using System.Collections.Generic;
|
2019-11-17 15:00:33 +00:00
|
|
|
using System.IO;
|
|
|
|
using System.IO.Compression;
|
2019-11-03 15:26:51 +00:00
|
|
|
using System.Linq;
|
2019-11-15 23:13:27 +00:00
|
|
|
using System.Reactive.Subjects;
|
2019-12-04 01:26:26 +00:00
|
|
|
using System.Threading.Tasks;
|
2019-11-03 16:43:43 +00:00
|
|
|
using Wabbajack.Common;
|
2019-11-03 15:26:51 +00:00
|
|
|
using Wabbajack.Lib.CompilationSteps;
|
2019-11-17 15:00:33 +00:00
|
|
|
using Wabbajack.Lib.Downloaders;
|
|
|
|
using Wabbajack.Lib.ModListRegistry;
|
2020-03-22 21:55:31 +00:00
|
|
|
using Wabbajack.Lib.Validation;
|
2019-11-15 13:06:34 +00:00
|
|
|
using Wabbajack.VirtualFileSystem;
|
2019-11-17 15:00:33 +00:00
|
|
|
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
|
|
|
using File = Alphaleonis.Win32.Filesystem.File;
|
|
|
|
using Path = Alphaleonis.Win32.Filesystem.Path;
|
2019-11-03 15:26:51 +00:00
|
|
|
|
|
|
|
namespace Wabbajack.Lib
|
|
|
|
{
|
2019-11-17 23:48:32 +00:00
|
|
|
public abstract class ACompiler : ABatchProcessor
|
2019-11-03 15:26:51 +00:00
|
|
|
{
|
2020-03-25 12:47:25 +00:00
|
|
|
public string ModListName, ModListAuthor, ModListDescription, ModListWebsite;
|
2020-03-25 22:30:43 +00:00
|
|
|
public AbsolutePath ModListImage, ModListReadme;
|
2019-12-20 07:14:43 +00:00
|
|
|
public bool ReadmeIsWebsite;
|
2020-03-22 21:55:31 +00:00
|
|
|
protected Version WabbajackVersion;
|
2019-11-17 14:30:06 +00:00
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
public abstract AbsolutePath VFSCacheName { get; }
|
2020-02-24 15:24:13 +00:00
|
|
|
//protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin");
|
2019-11-15 23:13:27 +00:00
|
|
|
/// <summary>
|
|
|
|
/// A stream of tuples of ("Update Title", 0.25) which represent the name of the current task
|
|
|
|
/// and the current progress.
|
|
|
|
/// </summary>
|
|
|
|
public IObservable<(string, float)> ProgressUpdates => _progressUpdates;
|
|
|
|
protected readonly Subject<(string, float)> _progressUpdates = new Subject<(string, float)>();
|
|
|
|
|
2019-11-24 00:30:51 +00:00
|
|
|
public abstract ModManager ModManager { get; }
|
2019-11-03 15:26:51 +00:00
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
public abstract AbsolutePath GamePath { get; }
|
2019-11-03 15:26:51 +00:00
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
public abstract AbsolutePath ModListOutputFolder { get; }
|
|
|
|
public abstract AbsolutePath ModListOutputFile { get; }
|
2019-11-03 16:43:43 +00:00
|
|
|
|
2020-01-20 01:36:09 +00:00
|
|
|
public bool IgnoreMissingFiles { get; set; }
|
|
|
|
|
2019-12-03 21:13:29 +00:00
|
|
|
public ICollection<Archive> SelectedArchives = new List<Archive>();
|
2019-11-15 05:47:31 +00:00
|
|
|
public List<Directive> InstallDirectives = new List<Directive>();
|
2019-11-15 13:06:34 +00:00
|
|
|
public List<RawSourceFile> AllFiles = new List<RawSourceFile>();
|
2019-11-15 05:47:31 +00:00
|
|
|
public ModList ModList = new ModList();
|
2019-11-15 14:19:39 +00:00
|
|
|
|
2019-11-15 13:06:34 +00:00
|
|
|
public List<IndexedArchive> IndexedArchives = new List<IndexedArchive>();
|
2020-03-22 15:50:53 +00:00
|
|
|
public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new Dictionary<Hash, IEnumerable<VirtualFile>>();
|
2019-11-03 15:26:51 +00:00
|
|
|
|
2020-01-20 01:36:09 +00:00
|
|
|
public static void Info(string msg)
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
|
|
|
Utils.Log(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Status(string msg)
|
|
|
|
{
|
2020-02-08 04:35:08 +00:00
|
|
|
Queue.Report(msg, Percent.Zero);
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 01:36:09 +00:00
|
|
|
public static void Error(string msg)
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
|
|
|
Utils.Log(msg);
|
|
|
|
throw new Exception(msg);
|
|
|
|
}
|
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
internal RelativePath IncludeId()
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-03-25 12:47:25 +00:00
|
|
|
return RelativePath.RandomFileName();
|
|
|
|
}
|
|
|
|
|
|
|
|
internal async Task<RelativePath> IncludeFile(byte[] data)
|
|
|
|
{
|
|
|
|
var id = IncludeId();
|
|
|
|
await ModListOutputFolder.Combine(id).WriteAllBytesAsync(data);
|
2019-11-17 15:00:33 +00:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
internal FileStream IncludeFile(out RelativePath id)
|
2020-03-04 05:23:08 +00:00
|
|
|
{
|
2020-03-25 12:47:25 +00:00
|
|
|
id = IncludeId();
|
|
|
|
return ModListOutputFolder.Combine(id).Create();
|
2020-03-04 05:23:08 +00:00
|
|
|
}
|
|
|
|
|
2020-03-25 12:47:25 +00:00
|
|
|
internal async Task<RelativePath> IncludeFile(string data)
|
|
|
|
{
|
|
|
|
var id = IncludeId();
|
|
|
|
await ModListOutputFolder.Combine(id).WriteAllTextAsync(data);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
internal async Task<RelativePath> IncludeFile(AbsolutePath data)
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-03-25 12:47:25 +00:00
|
|
|
var id = IncludeId();
|
2020-03-25 22:30:43 +00:00
|
|
|
await data.CopyToAsync(ModListOutputFolder.Combine(id));
|
2019-11-17 15:00:33 +00:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2020-03-04 12:10:49 +00:00
|
|
|
public async Task<bool> GatherMetaData()
|
|
|
|
{
|
|
|
|
Utils.Log($"Getting meta data for {SelectedArchives.Count} archives");
|
|
|
|
await SelectedArchives.PMap(Queue, async a =>
|
|
|
|
{
|
|
|
|
if (a.State is IMetaState metaState)
|
|
|
|
{
|
2020-04-04 22:06:14 +00:00
|
|
|
if (metaState.URL == null)
|
2020-04-04 17:53:05 +00:00
|
|
|
return;
|
|
|
|
|
2020-03-04 12:10:49 +00:00
|
|
|
var b = await metaState.LoadMetaData();
|
|
|
|
Utils.Log(b
|
|
|
|
? $"Getting meta data for {a.Name} was successful!"
|
|
|
|
: $"Getting meta data for {a.Name} failed!");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Utils.Log($"Archive {a.Name} is not an AbstractMetaState!");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-01 23:59:22 +00:00
|
|
|
public async Task ExportModList()
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-01-13 21:11:07 +00:00
|
|
|
Utils.Log($"Exporting ModList to {ModListOutputFile}");
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2020-01-13 21:11:07 +00:00
|
|
|
// Modify readme and ModList image to relative paths if they exist
|
2020-03-25 22:30:43 +00:00
|
|
|
if (ModListImage.Exists)
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
ModList.Image = (RelativePath)"modlist-image.png";
|
2019-12-03 04:56:06 +00:00
|
|
|
}
|
2020-03-25 22:30:43 +00:00
|
|
|
if (ModListReadme.Exists)
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
ModList.Readme = $"readme{ModListReadme.Extension}";
|
2019-12-03 04:56:06 +00:00
|
|
|
}
|
|
|
|
|
2020-01-04 03:56:20 +00:00
|
|
|
ModList.ReadmeIsWebsite = ReadmeIsWebsite;
|
2019-12-20 07:14:43 +00:00
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
using (var of = ModListOutputFolder.Combine("modlist").Create())
|
2020-03-22 21:55:31 +00:00
|
|
|
of.WriteAsMessagePack(ModList);
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
ModListOutputFile.Delete();
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
using (var fs = ModListOutputFile.Create())
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
|
|
|
using (var za = new ZipArchive(fs, ZipArchiveMode.Create))
|
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
ModListOutputFolder.EnumerateFiles()
|
2019-11-17 15:00:33 +00:00
|
|
|
.DoProgress("Compressing ModList",
|
|
|
|
f =>
|
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
var ze = za.CreateEntry((string)f.FileName);
|
|
|
|
using var os = ze.Open();
|
|
|
|
using var ins = f.OpenRead();
|
|
|
|
ins.CopyTo(os);
|
2019-11-17 15:00:33 +00:00
|
|
|
});
|
2019-12-03 04:56:06 +00:00
|
|
|
|
|
|
|
// Copy in modimage
|
2020-03-25 22:30:43 +00:00
|
|
|
if (ModListImage.Exists)
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
var ze = za.CreateEntry((string)ModList.Image);
|
2019-12-03 04:56:06 +00:00
|
|
|
using (var os = ze.Open())
|
2020-03-25 22:30:43 +00:00
|
|
|
using (var ins = ModListImage.OpenRead())
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
|
|
|
ins.CopyTo(os);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy in readme
|
2020-03-25 22:30:43 +00:00
|
|
|
if (ModListReadme.Exists)
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
|
|
|
var ze = za.CreateEntry(ModList.Readme);
|
|
|
|
using (var os = ze.Open())
|
2020-03-25 22:30:43 +00:00
|
|
|
using (var ins = ModListReadme.OpenRead())
|
2019-12-03 04:56:06 +00:00
|
|
|
{
|
|
|
|
ins.CopyTo(os);
|
|
|
|
}
|
|
|
|
}
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Utils.Log("Exporting ModList metadata");
|
2019-11-29 05:52:33 +00:00
|
|
|
var metadata = new DownloadMetadata
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
Size = ModListOutputFile.Size,
|
2019-11-17 15:00:33 +00:00
|
|
|
Hash = ModListOutputFile.FileHash(),
|
|
|
|
NumberOfArchives = ModList.Archives.Count,
|
|
|
|
SizeOfArchives = ModList.Archives.Sum(a => a.Size),
|
|
|
|
NumberOfInstalledFiles = ModList.Directives.Count,
|
|
|
|
SizeOfInstalledFiles = ModList.Directives.Sum(a => a.Size)
|
|
|
|
};
|
|
|
|
metadata.ToJSON(ModListOutputFile + ".meta.json");
|
2019-11-03 15:26:51 +00:00
|
|
|
|
2019-11-17 15:00:33 +00:00
|
|
|
|
|
|
|
Utils.Log("Removing ModList staging folder");
|
2020-04-01 23:59:22 +00:00
|
|
|
await Utils.DeleteDirectory(ModListOutputFolder);
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
|
|
|
|
2020-02-01 12:13:12 +00:00
|
|
|
public void GenerateManifest()
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-02-01 12:13:12 +00:00
|
|
|
var manifest = new Manifest(ModList);
|
|
|
|
manifest.ToJSON(ModListOutputFile + ".manifest.json");
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 01:26:26 +00:00
|
|
|
public async Task GatherArchives()
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
|
|
|
Info("Building a list of archives based on the files required");
|
|
|
|
|
2020-03-22 15:50:53 +00:00
|
|
|
var hashes = InstallDirectives.OfType<FromArchive>()
|
2020-03-28 02:54:14 +00:00
|
|
|
.Select(a => a.ArchiveHashPath.BaseHash)
|
2019-11-17 15:00:33 +00:00
|
|
|
.Distinct();
|
|
|
|
|
|
|
|
var archives = IndexedArchives.OrderByDescending(f => f.File.LastModified)
|
|
|
|
.GroupBy(f => f.File.Hash)
|
|
|
|
.ToDictionary(f => f.Key, f => f.First());
|
|
|
|
|
2020-03-22 15:50:53 +00:00
|
|
|
SelectedArchives = await hashes.PMap(Queue, hash => ResolveArchive(hash, archives));
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
|
|
|
|
2020-03-22 15:50:53 +00:00
|
|
|
public async Task<Archive> ResolveArchive(Hash hash, IDictionary<Hash, IndexedArchive> archives)
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2020-03-22 15:50:53 +00:00
|
|
|
if (archives.TryGetValue(hash, out var found))
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
2019-12-13 00:40:21 +00:00
|
|
|
return await ResolveArchive(found);
|
2019-12-12 23:24:27 +00:00
|
|
|
}
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2020-03-22 15:50:53 +00:00
|
|
|
Error($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen");
|
2019-12-12 23:24:27 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-12-13 00:40:21 +00:00
|
|
|
public async Task<Archive> ResolveArchive(IndexedArchive archive)
|
2019-12-12 23:24:27 +00:00
|
|
|
{
|
2019-12-22 18:14:48 +00:00
|
|
|
Utils.Status($"Checking link for {archive.Name}", alsoLog: true);
|
|
|
|
|
2019-12-12 23:24:27 +00:00
|
|
|
if (archive.IniData == null)
|
|
|
|
Error(
|
|
|
|
$"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again.");
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2019-12-12 23:24:27 +00:00
|
|
|
var result = new Archive
|
|
|
|
{
|
2019-12-23 00:03:45 +00:00
|
|
|
State = await DownloadDispatcher.ResolveArchive(archive.IniData)
|
2019-12-12 23:24:27 +00:00
|
|
|
};
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2019-12-12 23:24:27 +00:00
|
|
|
if (result.State == null)
|
|
|
|
Error($"{archive.Name} could not be handled by any of the downloaders");
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2019-12-12 23:24:27 +00:00
|
|
|
result.Name = archive.Name;
|
|
|
|
result.Hash = archive.File.Hash;
|
|
|
|
result.Meta = archive.Meta;
|
|
|
|
result.Size = archive.File.Size;
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2020-01-13 22:55:55 +00:00
|
|
|
if (result.State != null && !await result.State.Verify(result))
|
2019-12-12 23:24:27 +00:00
|
|
|
Error(
|
|
|
|
$"Unable to resolve link for {archive.Name}. If this is hosted on the Nexus the file may have been removed.");
|
2019-11-17 15:00:33 +00:00
|
|
|
|
2019-12-12 23:24:27 +00:00
|
|
|
return result;
|
2019-11-17 15:00:33 +00:00
|
|
|
}
|
2019-11-03 15:26:51 +00:00
|
|
|
|
2019-12-04 01:26:26 +00:00
|
|
|
public async Task<Directive> RunStack(IEnumerable<ICompilationStep> stack, RawSourceFile source)
|
2019-11-17 15:00:33 +00:00
|
|
|
{
|
|
|
|
Utils.Status($"Compiling {source.Path}");
|
|
|
|
foreach (var step in stack)
|
|
|
|
{
|
2019-12-04 01:26:26 +00:00
|
|
|
var result = await step.Run(source);
|
2019-11-17 15:00:33 +00:00
|
|
|
if (result != null) return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new InvalidDataException("Data fell out of the compilation stack");
|
|
|
|
}
|
|
|
|
|
2019-11-03 15:26:51 +00:00
|
|
|
public abstract IEnumerable<ICompilationStep> GetStack();
|
|
|
|
public abstract IEnumerable<ICompilationStep> MakeStack();
|
2020-01-20 01:36:09 +00:00
|
|
|
|
|
|
|
public static void PrintNoMatches(ICollection<NoMatch> noMatches)
|
|
|
|
{
|
|
|
|
const int max = 10;
|
|
|
|
Info($"No match for {noMatches.Count} files");
|
|
|
|
if (noMatches.Count > 0)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
foreach (var file in noMatches)
|
|
|
|
{
|
|
|
|
if (count++ < max)
|
|
|
|
{
|
|
|
|
Utils.Log($" {file.To} - {file.Reason}");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Utils.LogStraightToFile($" {file.To} - {file.Reason}");
|
|
|
|
}
|
|
|
|
if (count == max && noMatches.Count > max)
|
|
|
|
{
|
|
|
|
Utils.Log($" ...");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool CheckForNoMatchExit(ICollection<NoMatch> noMatches)
|
|
|
|
{
|
|
|
|
if (noMatches.Count > 0)
|
|
|
|
{
|
|
|
|
if (IgnoreMissingFiles)
|
|
|
|
{
|
|
|
|
Info("Continuing even though files were missing at the request of the user.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Info("Exiting due to no way to compile these files");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2019-11-03 15:26:51 +00:00
|
|
|
}
|
|
|
|
}
|