wabbajack/Wabbajack.Lib/zEditIntegration.cs

184 lines
6.8 KiB
C#
Raw Normal View History

2019-10-07 17:33:34 +00:00
using Alphaleonis.Win32.Filesystem;
using System;
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
{
private static MO2Compiler _mo2Compiler;
2019-11-11 20:05:23 +00:00
public static string FindzEditPath(ACompiler compiler)
2019-09-23 21:37:10 +00:00
{
_mo2Compiler = (MO2Compiler) compiler;
2019-11-11 20:05:23 +00:00
var executables = _mo2Compiler.MO2Ini.customExecutables;
2019-09-24 04:20:24 +00:00
if (executables.size == null) return null;
2019-09-23 21:37:10 +00:00
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 Path.GetDirectoryName(path);
}
return null;
}
public class IncludeZEditPatches : ACompilationStep
2019-09-23 21:37:10 +00:00
{
private Dictionary<string, zEditMerge> _mergesIndexed;
2019-09-23 21:37:10 +00:00
public IncludeZEditPatches(ACompiler compiler) : base(compiler)
{
var zEditPath = FindzEditPath(compiler);
var havezEdit = zEditPath != null;
2019-09-23 21:37:10 +00:00
Utils.Log(havezEdit ? $"Found zEdit at {zEditPath}" : $"zEdit not detected, disabling zEdit routines");
2019-09-23 21:37:10 +00:00
if (!havezEdit)
2019-10-07 17:33:34 +00:00
{
_mergesIndexed = new Dictionary<string, zEditMerge>();
return;
}
2019-09-23 21:37:10 +00:00
var merges = Directory.EnumerateFiles(Path.Combine(zEditPath, "profiles"),
DirectoryEnumerationOptions.Files | DirectoryEnumerationOptions.Recursive)
.Where(f => f.EndsWith("\\merges.json"))
.SelectMany(f => f.FromJSON<List<zEditMerge>>())
.GroupBy(f => (f.name, f.filename));
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(
2019-11-11 20:05:23 +00:00
m => Path.Combine(_mo2Compiler.MO2Folder, "mods", m.Key.name, m.Key.filename),
m => m.First());
}
2019-09-23 21:37:10 +00:00
public override async ValueTask<Directive> Run(RawSourceFile source)
2019-09-23 21:37:10 +00:00
{
if (!_mergesIndexed.TryGetValue(source.AbsolutePath, out var merge)) return null;
var result = source.EvolveTo<MergedPatch>();
result.Sources = merge.plugins.Select(f =>
2019-09-23 21:37:10 +00:00
{
var orig_path = Path.Combine(f.dataFolder, f.filename);
var paths = new[]
{
orig_path,
orig_path + ".mohidden",
Path.Combine(Path.GetDirectoryName(orig_path), "optional", Path.GetFileName(orig_path))
};
var abs_path = paths.FirstOrDefault(p => File.Exists(p));
if (abs_path == null)
throw new InvalidDataException(
$"File {abs_path} is required to build {merge.filename} but it doesn't exist searched in: \n" + String.Join("\n", paths));
2019-09-23 21:37:10 +00:00
return new SourcePatch
2019-09-23 21:37:10 +00:00
{
2019-11-11 20:05:23 +00:00
RelativePath = abs_path.RelativeTo(_mo2Compiler.MO2Folder),
2019-11-15 13:06:34 +00:00
Hash = _compiler.VFS.Index.ByFullPath[abs_path].Hash
};
}).ToList();
2019-11-11 20:05:23 +00:00
var src_data = result.Sources.Select(f => File.ReadAllBytes(Path.Combine(_mo2Compiler.MO2Folder, f.RelativePath)))
.ConcatArrays();
2019-09-23 21:37:10 +00:00
var dst_data = File.ReadAllBytes(source.AbsolutePath);
2019-09-23 21:37:10 +00:00
using (var ms = new MemoryStream())
{
Utils.CreatePatch(src_data, dst_data, ms);
result.PatchID = _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(compiler);
}
}
2019-09-23 21:37:10 +00:00
}
2019-11-02 15:38:03 +00:00
public class zEditMerge
2019-09-23 21:37:10 +00:00
{
public string name;
public string filename;
public List<zEditMergePlugin> plugins;
}
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;
}
public static void VerifyMerges(MO2Compiler compiler)
2019-09-23 21:37:10 +00:00
{
var by_name = compiler.InstallDirectives.ToDictionary(f => f.To);
foreach (var directive in compiler.InstallDirectives.OfType<MergedPatch>())
{
foreach (var source in directive.Sources)
{
if (by_name.TryGetValue(source.RelativePath, out var result))
{
if (result.Hash != source.Hash)
throw new InvalidDataException($"Hashes for {result.To} needed for zEdit merge sources don't match, this shouldn't happen");
continue;
}
throw new InvalidDataException($"{source.RelativePath} is needed for merged patch {directive.To} but is not included in the install.");
}
}
}
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>()
.PMap(installer.Queue, m =>
2019-11-02 15:38:03 +00:00
{
Utils.LogStatus($"Generating zEdit merge: {m.To}");
2019-11-16 13:23:41 +00:00
var src_data = m.Sources.Select(s => File.ReadAllBytes(Path.Combine(installer.OutputFolder, s.RelativePath)))
2019-11-02 15:38:03 +00:00
.ConcatArrays();
var patch_data = installer.LoadBytesFromPath(m.PatchID);
2019-11-16 13:23:41 +00:00
using (var fs = File.OpenWrite(Path.Combine(installer.OutputFolder, m.To)))
2019-11-02 15:38:03 +00:00
BSDiff.Apply(new MemoryStream(src_data), () => new MemoryStream(patch_data), fs);
});
}
2019-09-23 21:37:10 +00:00
}
}