diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index 3c8a448c..7ca9e3a0 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -652,5 +652,10 @@ namespace Wabbajack.Common return d.Deserialize<T>(new StringReader(s)); } + public static void LogStatus(string s) + { + Status(s); + Log(s); + } } } \ No newline at end of file diff --git a/Wabbajack.Lib/Installer.cs b/Wabbajack.Lib/Installer.cs index a5bdeabb..cbedd716 100644 --- a/Wabbajack.Lib/Installer.cs +++ b/Wabbajack.Lib/Installer.cs @@ -66,7 +66,7 @@ namespace Wabbajack.Lib throw new Exception(msg); } - private byte[] LoadBytesFromPath(string path) + public byte[] LoadBytesFromPath(string path) { using (var fs = new FileStream(ModListArchive, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) @@ -155,6 +155,8 @@ namespace Wabbajack.Lib InstallIncludedFiles(); BuildBSAs(); + zEditIntegration.GenerateMerges(this); + Info("Installation complete! You may exit the program."); // Removed until we decide if we want this functionality // Nexus devs weren't sure this was a good idea, I (halgari) agree. diff --git a/Wabbajack.Lib/zEditIntegration.cs b/Wabbajack.Lib/zEditIntegration.cs index 93f1327e..386b13ad 100644 --- a/Wabbajack.Lib/zEditIntegration.cs +++ b/Wabbajack.Lib/zEditIntegration.cs @@ -115,7 +115,7 @@ namespace Wabbajack.Lib } } - class zEditMerge + public class zEditMerge { public string name; public string filename; @@ -123,7 +123,7 @@ namespace Wabbajack.Lib } - class zEditMergePlugin + public class zEditMergePlugin { public string filename; public string dataFolder; @@ -147,5 +147,24 @@ namespace Wabbajack.Lib } } } + + public static void GenerateMerges(Installer installer) + { + installer.ModList + .Directives + .OfType<MergedPatch>() + .PMap(m => + { + Utils.LogStatus($"Generating zEdit merge: {m.To}"); + + var src_data = m.Sources.Select(s => File.ReadAllBytes(Path.Combine(installer.Outputfolder, s.RelativePath))) + .ConcatArrays(); + + var patch_data = installer.LoadBytesFromPath(m.PatchID); + + using (var fs = File.OpenWrite(Path.Combine(installer.Outputfolder, m.To))) + BSDiff.Apply(new MemoryStream(src_data), () => new MemoryStream(patch_data), fs); + }); + } } } diff --git a/Wabbajack.Test/ACompilerTest.cs b/Wabbajack.Test/ACompilerTest.cs index bac2aeeb..9a0eb223 100644 --- a/Wabbajack.Test/ACompilerTest.cs +++ b/Wabbajack.Test/ACompilerTest.cs @@ -51,5 +51,20 @@ namespace Wabbajack.Test var compiler = new Compiler(utils.MO2Folder); return compiler; } + protected ModList CompileAndInstall(string profile) + { + var compiler = ConfigureAndRunCompiler(profile); + Install(compiler); + return compiler.ModList; + } + + protected void Install(Compiler compiler) + { + var modlist = Installer.LoadFromFile(compiler.ModListOutputFile); + var installer = new Installer(compiler.ModListOutputFile, modlist, utils.InstallFolder); + installer.DownloadFolder = utils.DownloadsFolder; + installer.GameFolder = utils.GameFolder; + installer.Install(); + } } } diff --git a/Wabbajack.Test/SanityTests.cs b/Wabbajack.Test/SanityTests.cs index 5c66ec7d..19115113 100644 --- a/Wabbajack.Test/SanityTests.cs +++ b/Wabbajack.Test/SanityTests.cs @@ -72,21 +72,5 @@ namespace Wabbajack.Test Assert.IsInstanceOfType(directive, typeof(PatchedFromArchive)); } - - private ModList CompileAndInstall(string profile) - { - var compiler = ConfigureAndRunCompiler(profile); - Install(compiler); - return compiler.ModList; - } - - private void Install(Compiler compiler) - { - var modlist = Installer.LoadFromFile(compiler.ModListOutputFile); - var installer = new Installer(compiler.ModListOutputFile, modlist, utils.InstallFolder); - installer.DownloadFolder = utils.DownloadsFolder; - installer.GameFolder = utils.GameFolder; - installer.Install(); - } } } diff --git a/Wabbajack.Test/Wabbajack.Test.csproj b/Wabbajack.Test/Wabbajack.Test.csproj index e3a063d3..50b69ed8 100644 --- a/Wabbajack.Test/Wabbajack.Test.csproj +++ b/Wabbajack.Test/Wabbajack.Test.csproj @@ -71,6 +71,9 @@ <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath> </Reference> + <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> + <HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> + </Reference> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> <Reference Include="ReactiveUI, Version=10.5.0.0, Culture=neutral, processorArchitecture=MSIL"> @@ -122,6 +125,7 @@ <Compile Include="ContentRightsManagementTests.cs" /> <Compile Include="CompilationStackTests.cs" /> <Compile Include="WebAutomationTests.cs" /> + <Compile Include="zEditIntegrationTests.cs" /> </ItemGroup> <ItemGroup> <None Include="app.config" /> diff --git a/Wabbajack.Test/ZEditIntegrationTests.cs b/Wabbajack.Test/ZEditIntegrationTests.cs new file mode 100644 index 00000000..15fff578 --- /dev/null +++ b/Wabbajack.Test/ZEditIntegrationTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Alphaleonis.Win32.Filesystem; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Wabbajack.Common; +using Wabbajack.Lib; + +namespace Wabbajack.Test +{ + [TestClass] + public class zEditIntegrationTests : ACompilerTest + { + [TestMethod] + public void CanCreatezEditPatches() + { + var profile = utils.AddProfile(); + var moda = utils.AddMod(); + var modb = utils.AddMod(); + var moddest = utils.AddMod(); + var srca = utils.AddModFile(moda, @"srca.esp", 10); + var srcb = utils.AddModFile(moda, @"srcb.esp", 10); + var srcc = utils.AddModFile(modb, @"srcd.esp", 10); + var dest = utils.AddModFile(moddest, @"merged.esp", 20); + + var srcs = new List<string> {srca, srcb, srcc}; + + + Directory.CreateDirectory(Path.Combine(utils.MO2Folder, "tools", "mator", "bleh", "profiles", "myprofile")); + new List<zEditIntegration.zEditMerge>() + { + new zEditIntegration.zEditMerge() + { + name = moddest, + filename = "merged.esp", + plugins = new List<zEditIntegration.zEditMergePlugin>() + { + new zEditIntegration.zEditMergePlugin() + { + filename = srca, + dataFolder = Path.GetDirectoryName(srca), + }, + new zEditIntegration.zEditMergePlugin() + { + filename = srcb, + dataFolder = Path.GetDirectoryName(srcb), + }, + new zEditIntegration.zEditMergePlugin() + { + filename = srcc, + dataFolder = Path.GetDirectoryName(srcc), + } + } + } + }.ToJSON(Path.Combine(utils.MO2Folder, "tools", "mator", "bleh", "profiles", "myprofile", "merges.json")); + + utils.Configure(); + + + utils.AddManualDownload( + new Dictionary<string, byte[]> { { "srca.esp", File.ReadAllBytes(srca) } }); + utils.AddManualDownload( + new Dictionary<string, byte[]> { { "srcb.esp", File.ReadAllBytes(srcb) } }); + utils.AddManualDownload( + new Dictionary<string, byte[]> { { "srcc.esp", File.ReadAllBytes(srcc) } }); + + File.AppendAllLines(Path.Combine(utils.MO2Folder, "ModOrganizer.ini"), + new List<string> + { + "[customExecutables]", + "size=1", + $@"1\binary={utils.MO2Folder.Replace('\\','/')}/tools/mator/bleh/zEdit.exe" + + }); + + var modlist = CompileAndInstall(profile); + var directive = modlist.Directives.Where(m => m.To == $"mods\\{moddest}\\merged.esp").FirstOrDefault(); + + Assert.IsNotNull(directive); + Assert.IsInstanceOfType(directive, typeof(MergedPatch)); + + var merged = directive as MergedPatch; + + foreach (var (source, path) in merged.Sources.Zip(srcs, (a, b) => (a, b))) + { + Assert.AreEqual(source.Hash, Utils.FileHash(path)); + } + + utils.VerifyInstalledFile(moddest, "merged.esp"); + + } + } +} diff --git a/Wabbajack.Test/packages.config b/Wabbajack.Test/packages.config index 87fd1b36..ce27ac3f 100644 --- a/Wabbajack.Test/packages.config +++ b/Wabbajack.Test/packages.config @@ -4,6 +4,7 @@ <package id="DynamicData" version="6.13.18" targetFramework="net472" /> <package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net472" /> <package id="MSTest.TestFramework" version="1.3.2" targetFramework="net472" /> + <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" /> <package id="ReactiveUI" version="10.5.7" targetFramework="net472" /> <package id="Splat" version="9.1.1" targetFramework="net472" /> <package id="Splat.Drawing" version="9.1.1" targetFramework="net472" />