wabbajack/Wabbajack.Lib/zEditIntegration.cs

278 lines
11 KiB
C#
Raw Normal View History

2019-10-07 17:33:34 +00:00
using Alphaleonis.Win32.Filesystem;
2019-09-23 21:37:10 +00:00
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
2019-09-23 21:37:10 +00:00
using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
2019-09-23 21:37:10 +00:00
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using File = Alphaleonis.Win32.Filesystem.File;
using Path = Alphaleonis.Win32.Filesystem.Path;
using System.Threading.Tasks;
2019-09-23 21:37:10 +00:00
namespace Wabbajack.Lib
2019-09-23 21:37:10 +00:00
{
public class zEditIntegration
{
public class IncludeZEditPatches : ACompilationStep
2019-09-23 21:37:10 +00:00
{
private readonly Dictionary<AbsolutePath, zEditMerge> _mergesIndexed = new Dictionary<AbsolutePath, zEditMerge>();
private readonly bool _disabled = true;
2019-09-23 21:37:10 +00:00
private readonly MO2Compiler _mo2Compiler;
public IncludeZEditPatches(MO2Compiler compiler) : base(compiler)
{
_mo2Compiler = compiler;
var zEditPath = FindzEditPath(compiler);
var found = zEditPath != default;
2019-09-23 21:37:10 +00:00
Utils.Log(found ? $"Found zEdit at {zEditPath}" : "zEdit not detected, disabling zEdit routines");
2019-09-23 21:37:10 +00:00
if (!found)
2019-10-07 17:33:34 +00:00
{
2020-04-03 22:56:14 +00:00
_mergesIndexed = new Dictionary<AbsolutePath, zEditMerge>();
return;
}
_mo2Compiler = compiler;
var settingsFiles = zEditPath.Parent.Combine("profiles").EnumerateFiles()
2020-04-04 22:06:14 +00:00
.Where(f => f.IsFile)
.Where(f => f.FileName == Consts.SettingsJson)
.Where(f =>
{
var settings = f.FromJson<zEditSettings>();
if (settings.modManager != "Mod Organizer 2")
{
Utils.Log($"zEdit settings file {f}: modManager is not Mod Organizer 2 but {settings.modManager}!");
return false;
}
if (settings.managerPath != _mo2Compiler.MO2Folder)
{
Utils.Log($"zEdit settings file {f}: managerPath is not {_mo2Compiler.MO2Folder} but {settings.managerPath}!");
return false;
}
2020-04-04 22:06:14 +00:00
if (settings.modsPath != _mo2Compiler.MO2Folder.Combine(Consts.MO2ModFolderName))
{
2020-04-04 17:10:13 +00:00
Utils.Log($"zEdit settings file {f}: modsPath is not {_mo2Compiler.MO2Folder}\\{Consts.MO2ModFolderName} but {settings.modsPath}!");
return false;
}
2020-04-04 22:06:14 +00:00
if (settings.mergePath != _mo2Compiler.MO2Folder.Combine(Consts.MO2ModFolderName))
{
2020-04-04 17:10:13 +00:00
Utils.Log($"zEdit settings file {f}: modsPath is not {_mo2Compiler.MO2Folder}\\{Consts.MO2ModFolderName} but {settings.modsPath}!");
return false;
}
return true;
2020-04-04 22:06:14 +00:00
}).ToList();
if (!settingsFiles.Any())
{
Utils.Log($"Found not acceptable settings.json file for zEdit!");
return;
}
var profileFolder =
2020-04-04 22:06:14 +00:00
settingsFiles.Where(x => x.Parent.Combine("merges.json").IsFile)
.Select(x => x == default ? x : x.Parent)
.FirstOrDefault();
2019-09-23 21:37:10 +00:00
2020-04-04 22:06:14 +00:00
if (profileFolder == default)
{
Utils.Log("Found no acceptable profiles folder for zEdit!");
return;
}
2020-04-04 22:06:14 +00:00
var mergeFile = profileFolder.Combine("merges.json");
Utils.Log($"Using merge file {mergeFile}");
var merges = mergeFile.FromJson<List<zEditMerge>>().GroupBy(f => (f.name, f.filename)).ToArray();
2019-09-23 21:37:10 +00:00
merges.Where(m => m.Count() > 1)
.Do(m =>
{
2019-12-04 04:12:08 +00:00
Utils.Log(
$"WARNING, you have two patches named {m.Key.name}\\{m.Key.filename} in your zEdit profiles. We'll pick one at random, this probably isn't what you want.");
});
_mergesIndexed =
merges.ToDictionary(
2020-04-03 22:56:14 +00:00
m => _mo2Compiler.MO2Folder.Combine((string)Consts.MO2ModFolderName, m.Key.name, m.Key.filename),
m => m.First());
_disabled = false;
}
2019-09-23 21:37:10 +00:00
public static AbsolutePath FindzEditPath(MO2Compiler compiler)
{
var executables = compiler.MO2Ini.customExecutables;
if (executables.size == null) return default;
foreach (var idx in Enumerable.Range(1, int.Parse(executables.size)))
{
var path = (string)executables[$"{idx}\\binary"];
if (path == null) continue;
if (path.EndsWith("zEdit.exe"))
return (AbsolutePath)path;
}
return default;
}
public override async ValueTask<Directive?> Run(RawSourceFile source)
2019-09-23 21:37:10 +00:00
{
if (_disabled) return null;
2020-04-04 16:30:42 +00:00
if (!_mergesIndexed.TryGetValue(source.AbsolutePath, out var merge))
{
2020-04-04 22:06:14 +00:00
if(source.AbsolutePath.Extension != Consts.SeqExtension)
2020-04-04 16:30:42 +00:00
return null;
2020-04-04 22:06:14 +00:00
var seqFolder = source.AbsolutePath.Parent;
2020-04-04 16:30:42 +00:00
2020-04-04 22:06:14 +00:00
if (seqFolder.FileName != (RelativePath)"seq")
2020-04-04 16:30:42 +00:00
return null;
2020-04-04 22:06:14 +00:00
var mergeFolder = seqFolder.Parent;
var mergeName = mergeFolder.FileName;
2020-04-04 16:30:42 +00:00
2020-04-04 22:06:14 +00:00
if (!mergeFolder.Combine(mergeName + ".esp").Exists)
2020-04-04 16:30:42 +00:00
return null;
var inline = source.EvolveTo<InlineFile>();
2020-04-04 22:06:14 +00:00
inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync());
2020-04-04 16:30:42 +00:00
return inline;
}
var result = source.EvolveTo<MergedPatch>();
2020-04-10 01:29:53 +00:00
result.Sources.SetTo(merge.plugins.Select(f =>
2019-09-23 21:37:10 +00:00
{
2020-04-03 22:56:14 +00:00
var origPath = (AbsolutePath)Path.Combine(f.dataFolder, f.filename);
var paths = new[]
{
origPath,
2020-04-03 22:56:14 +00:00
origPath.WithExtension(new Extension(".mohidden")),
origPath.Parent.Combine((RelativePath)"optional", origPath.FileName)
};
2020-04-03 22:56:14 +00:00
var absPath = paths.FirstOrDefault(file => file.IsFile);
2020-04-03 22:56:14 +00:00
if (absPath == default)
throw new InvalidDataException(
$"File {origPath} is required to build {merge.filename} but it doesn't exist searched in: \n" + string.Join("\n", paths));
2020-04-03 22:56:14 +00:00
Hash hash;
try
{
2020-04-03 22:56:14 +00:00
hash = _compiler.VFS.Index.ByRootPath[absPath].Hash;
2020-04-10 01:29:53 +00:00
}
catch (KeyNotFoundException e)
{
2020-04-10 01:29:53 +00:00
Utils.Error(e, $"Could not find the key {absPath} in the VFS Index dictionary!");
throw;
}
2019-09-23 21:37:10 +00:00
return new SourcePatch
2019-09-23 21:37:10 +00:00
{
RelativePath = absPath.RelativeTo(_mo2Compiler.MO2Folder),
Hash = hash
};
2020-04-10 01:29:53 +00:00
}));
2020-04-03 22:56:14 +00:00
var srcData = result.Sources.Select(f => _mo2Compiler.MO2Folder.Combine(f.RelativePath).ReadAllBytes())
.ConcatArrays();
2019-09-23 21:37:10 +00:00
2020-04-03 22:56:14 +00:00
var dstData = await source.AbsolutePath.ReadAllBytesAsync();
2019-09-23 21:37:10 +00:00
2020-02-05 05:17:12 +00:00
await using (var ms = new MemoryStream())
{
2020-04-03 22:56:14 +00:00
await Utils.CreatePatch(srcData, dstData, ms);
result.PatchID = await _compiler.IncludeFile(ms.ToArray());
2019-09-23 21:37:10 +00:00
}
return result;
2019-09-23 21:37:10 +00:00
}
public override IState GetState()
{
return new State();
}
[JsonObject("IncludeZEditPatches")]
public class State : IState
{
public ICompilationStep CreateStep(ACompiler compiler)
{
return new IncludeZEditPatches((MO2Compiler)compiler);
}
}
2019-09-23 21:37:10 +00:00
}
public class zEditSettings
{
public string modManager = string.Empty;
2020-04-04 22:06:14 +00:00
public AbsolutePath managerPath;
public AbsolutePath modsPath;
public AbsolutePath mergePath;
}
2019-11-02 15:38:03 +00:00
public class zEditMerge
2019-09-23 21:37:10 +00:00
{
public string name = string.Empty;
public string filename = string.Empty;
public List<zEditMergePlugin> plugins = new List<zEditMergePlugin>();
2019-09-23 21:37:10 +00:00
}
2019-11-02 15:38:03 +00:00
public class zEditMergePlugin
2019-09-23 21:37:10 +00:00
{
public string? filename;
public string? dataFolder;
2019-09-23 21:37:10 +00:00
}
public static void VerifyMerges(MO2Compiler compiler)
2019-09-23 21:37:10 +00:00
{
2020-04-04 16:30:42 +00:00
var byName = compiler.InstallDirectives.ToDictionary(f => f.To);
2019-09-23 21:37:10 +00:00
foreach (var directive in compiler.InstallDirectives.OfType<MergedPatch>())
{
foreach (var source in directive.Sources)
{
2020-04-04 16:30:42 +00:00
if (!byName.TryGetValue(source.RelativePath, out var result))
throw new InvalidDataException(
$"{source.RelativePath} is needed for merged patch {directive.To} but is not included in the install.");
if (result.Hash != source.Hash)
throw new InvalidDataException($"Hashes for {result.To} needed for zEdit merge sources don't match, this shouldn't happen");
2019-09-23 21:37:10 +00:00
}
}
}
2019-11-02 15:38:03 +00:00
public static async Task GenerateMerges(MO2Installer installer)
2019-11-02 15:38:03 +00:00
{
await installer.ModList
2019-11-02 15:38:03 +00:00
.Directives
.OfType<MergedPatch>()
2020-04-03 22:56:14 +00:00
.PMap(installer.Queue, async m =>
2019-11-02 15:38:03 +00:00
{
Utils.LogStatus($"Generating zEdit merge: {m.To}");
2020-04-03 22:56:14 +00:00
var srcData = m.Sources.Select(s => installer.OutputFolder.Combine(s.RelativePath).ReadAllBytes())
2019-11-02 15:38:03 +00:00
.ConcatArrays();
2020-04-03 22:56:14 +00:00
var patchData = await installer.LoadBytesFromPath(m.PatchID);
2019-11-02 15:38:03 +00:00
2020-04-03 22:56:14 +00:00
await using var fs = installer.OutputFolder.Combine(m.To).Create();
Utils.ApplyPatch(new MemoryStream(srcData), () => new MemoryStream(patchData), fs);
2019-11-02 15:38:03 +00:00
});
}
2019-09-23 21:37:10 +00:00
}
}