diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index ba62db3..1877a2a 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -19,23 +19,28 @@ on: - 'mkdocs.yml' jobs: - build-test: + build-test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - sln: - - MutagenMerger.sln - + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.* - name: Install dependencies run: | - dotnet clean ${{ matrix.sln }} -c Release && dotnet nuget locals all --clear - dotnet restore ${{ matrix.sln }} + dotnet clean -c Release && dotnet nuget locals all --clear + dotnet restore + dotnet tool restore - name: Build - run: dotnet build ${{ matrix.sln }} -c Release --no-restore /p:GeneratePackageOnBuild=false + run: dotnet build -c Release --no-restore /p:GeneratePackageOnBuild=false - name: Test - run: dotnet test ${{ matrix.sln }} -c Release --no-build + run: dotnet test -c Release --no-build diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d02544a --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + true + enable + true + GPL-3.0-only + https://github.com/Mutagen-Modding/Mutagen.Bethesda.Merge + https://github.com/Mutagen-Modding/Mutagen.Bethesda.Merge + GPL-3.0-only + 2025 + Mutagen + Mutagen + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..e1f31ab --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + diff --git a/Mutagen.Bethesda.Merge.CLI/MainModule.cs b/Mutagen.Bethesda.Merge.CLI/MainModule.cs new file mode 100644 index 0000000..3e0cb61 --- /dev/null +++ b/Mutagen.Bethesda.Merge.CLI/MainModule.cs @@ -0,0 +1,15 @@ +using System.IO.Abstractions; +using Autofac; +using Mutagen.Bethesda.Merge.Lib; + +namespace Mutagen.Bethesda.Merge.CLI; + +public class MainModule : Autofac.Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As() + .SingleInstance(); + builder.RegisterModule(); + } +} diff --git a/Mutagen.Bethesda.Merge.CLI/Mutagen.Bethesda.Merge.CLI.csproj b/Mutagen.Bethesda.Merge.CLI/Mutagen.Bethesda.Merge.CLI.csproj new file mode 100644 index 0000000..1d8f272 --- /dev/null +++ b/Mutagen.Bethesda.Merge.CLI/Mutagen.Bethesda.Merge.CLI.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + + + + + + + + + + + + diff --git a/Mutagen.Bethesda.Merge.CLI/Options.cs b/Mutagen.Bethesda.Merge.CLI/Options.cs new file mode 100644 index 0000000..c17fc5a --- /dev/null +++ b/Mutagen.Bethesda.Merge.CLI/Options.cs @@ -0,0 +1,24 @@ +using CommandLine; + +namespace Mutagen.Bethesda.Merge.CLI; + +public class Options +{ + [Option("game", HelpText = "Game to mod (Default SkyrimSE)")] + public GameRelease Game { get; set; } = GameRelease.SkyrimSE; + + [Option("data", HelpText = "Path to the data folder", Required = true)] + public string DataFolder { get; set; } = string.Empty; + + [Option("merge", Min = 1, Max = 4096, HelpText = "Plugins to merge")] + public IEnumerable PluginsToMerge { get; set; } = []; + + [Option("mergefile", HelpText = "Get plugins to merge from file")] + public string PluginsMergeTxt { get; set; } = string.Empty; + + [Option("output", Required = true, HelpText = "Output merge folder")] + public string Output { get; set; } = string.Empty; + + [Option("mergename", Required = true, HelpText = "Name of Merge")] + public string MergeName { get; set; } = string.Empty; +} diff --git a/Mutagen.Bethesda.Merge.CLI/Program.cs b/Mutagen.Bethesda.Merge.CLI/Program.cs new file mode 100644 index 0000000..58c9f35 --- /dev/null +++ b/Mutagen.Bethesda.Merge.CLI/Program.cs @@ -0,0 +1,68 @@ +using System.Diagnostics; +using Autofac; +using CommandLine; +using Mutagen.Bethesda.Environments.DI; +using Mutagen.Bethesda.Fallout4; +using Mutagen.Bethesda.Merge.Lib.DI; +using Mutagen.Bethesda.Oblivion; +using Mutagen.Bethesda.Plugins; +using Mutagen.Bethesda.Skyrim; + +namespace Mutagen.Bethesda.Merge.CLI; + +public static class Program +{ + public static async Task Main(string[] args) + { + await Parser.Default.ParseArguments(args) + .WithParsedAsync(Run); + } + + private static async Task Run(Options options) + { + var modsToMerge = options.PluginsMergeTxt != string.Empty + ? (await File.ReadAllLinesAsync(options.PluginsMergeTxt)) + .Select(x => ModKey.FromNameAndExtension(x)) + .ToList() + : options.PluginsToMerge + .Select(x => ModKey.FromNameAndExtension(x)) + .ToList(); + + if (Directory.Exists(options.Output)) Directory.Delete(options.Output, true); + + var sw = new Stopwatch(); + sw.Start(); + + Type[] genericTypes; + switch (options.Game.ToCategory()) + { + case GameCategory.Oblivion: + genericTypes = new Type[] { typeof(IOblivionMod), typeof(IOblivionModGetter),typeof(IOblivionMajorRecord), typeof(IOblivionMajorRecordGetter) }; + break; + case GameCategory.Fallout4: + genericTypes = new Type[] { typeof(IFallout4Mod), typeof(IFallout4ModGetter), typeof(IFallout4MajorRecord), typeof(IFallout4MajorRecordGetter) }; + break; + case GameCategory.Skyrim: + default: + genericTypes = new Type[] { typeof(ISkyrimMod), typeof(ISkyrimModGetter), typeof(ISkyrimMajorRecord), typeof(ISkyrimMajorRecordGetter) }; + break; + } + + ContainerBuilder builder = new(); + builder.RegisterModule(); + builder.RegisterInstance( + new DataDirectoryInjection(options.DataFolder)); + builder.RegisterInstance( + new GameReleaseInjection(options.Game)); + var container = builder.Build(); + var merger = container.Resolve(typeof(Merger<,,,>).MakeGenericType(genericTypes)) as IMerger; + + merger!.Merge( + modsToMerge, + ModKey.FromNameAndExtension(options.MergeName), + options.Output); + + Console.WriteLine($"Merged {modsToMerge.Count} plugins in {sw.ElapsedMilliseconds}ms"); + sw.Stop(); + } +} diff --git a/Mutagen.Bethesda.Merge.Lib/DI/AssetMerge.cs b/Mutagen.Bethesda.Merge.Lib/DI/AssetMerge.cs new file mode 100644 index 0000000..b7381f6 --- /dev/null +++ b/Mutagen.Bethesda.Merge.Lib/DI/AssetMerge.cs @@ -0,0 +1,254 @@ +using System.IO.Abstractions; +using Microsoft.Extensions.FileSystemGlobbing; +using Mutagen.Bethesda.Archives; +using Mutagen.Bethesda.Plugins; +using Mutagen.Bethesda.Plugins.Masters; +using Mutagen.Bethesda.Plugins.Records; +using Noggog; +using Noggog.IO; +using DirectoryInfoWrapper = Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoWrapper; + +namespace Mutagen.Bethesda.Merge.Lib.DI; + +public class AssetMerge + where TModGetter : class, IModGetter, IMajorRecordContextEnumerable, IMajorRecordGetterEnumerable, IContextGetterMod + where TMod : class, IMod, IContextMod, TModGetter + where TMajorRecord : class, IMajorRecord, TMajorRecordGetter + where TMajorRecordGetter : class, IMajorRecordGetter +{ + private readonly IFileSystem _fileSystem; + private readonly MergeState _mergeState; + private readonly string _outputDir; + private readonly string _mergeName; + private readonly List _rules; + + public delegate AssetMerge Factory( + MergeState mergeState); + + public AssetMerge( + MergeState mergeState, + IFileSystem fileSystem) + { + _fileSystem = fileSystem; + _mergeState = mergeState; + _rules = GetRules(); + _outputDir = Path.GetDirectoryName(_mergeState.OutputPath) ?? ""; + _mergeName = Path.GetFileName(_mergeState.OutputPath); + } + + private List GetRules() + { + var rules = new List() {"**/*.@(esp|esm|bsa|ba2|bsl)", "meta.ini", + "interface/translations/*.txt", "TES5Edit Backups/**/*", + "fomod/**/*", "screenshot?(s)/**/*", "scripts/source/*.psc", "source/scripts/*.psc"}; + + _mergeState.ModsToMerge.ForEach(x => + { + rules.Add($"**/{x.Name.ToLower()}.seq"); + rules.Add($"**/{x.Name.ToLower()}.ini"); + rules.Add($"**/{x.Name.ToLower()}_DISTR.ini"); + rules.Add($"**/{x.Name.ToLower()}_ANIO.ini"); + rules.Add($"**/{x.Name.ToLower()}_SWAP.ini"); + rules.Add($"**/{x.Name.ToLower()}_KID.ini"); + rules.Add($"**/{x.FileName.String.ToLower()}/**/*"); + }); + return rules; + } + + public void Handle() + { + using var temp = TempFolder.Factory(); + var matcher = new Matcher(); + matcher.AddIncludePatterns(new string[] { "**/*" }); + matcher.AddExcludePatterns(_rules); + Parallel.ForEach(_mergeState.ModsToMerge, mod => { + var bsaPattern = mod.FileName.NameWithoutExtension + "*." + (_mergeState.Release == GameRelease.Fallout4 ? "b2a" : "bsa"); + string[] bsaFiles = _fileSystem.Directory.GetFiles(_mergeState.DataPath, bsaPattern); + + // bsaFiles.ForEach(Console.WriteLine); + + foreach (string bsa in bsaFiles) + { + ExtractBSA(bsa, temp.Dir); + } + // Console.WriteLine(); + }); + + var matches = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(temp.Dir))); + + Parallel.ForEach(matches.Files, file => { + _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(_outputDir, file.Path)) ?? ""); + Console.WriteLine(" Copying extracted asset \"" + file.Path + "\""); + _fileSystem.File.Copy(Path.Combine(temp.Dir, file.Path), Path.Combine(_outputDir, file.Path)); + }); + + foreach (var mod in _mergeState.ModsToMerge) + { + CopyAssets(_mergeState.DataPath, mod); + CopyAssets(temp.Dir, mod); + } + + BuildSeqFile(_mergeState.DataPath, temp.Dir, _mergeState.OutgoingMod); + } + + private void BuildSeqFile(string dataPath, DirectoryPath temp, TMod outputMod) + { + var formIds = GetSeqQuests(outputMod); + if (formIds.Count == 0) return; + var fileName = _mergeName.Substring(0, _mergeName.Length - 4) + ".seq"; + var filePath = Path.Combine(_outputDir, "seq", fileName); + var buffer = new byte[formIds.Count * sizeof(UInt32)]; + + for (int i = 0; i < formIds.Count; i++) + { + Buffer.BlockCopy(BitConverter.GetBytes(formIds[i]), 0, buffer, i * 4, 4); + } + if (!BitConverter.IsLittleEndian) Array.Reverse(buffer); + _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? ""); + _fileSystem.File.WriteAllBytes(filePath, buffer); + Console.WriteLine(); + Console.WriteLine(" Created SEQ file: " + fileName); + + // if (!formIds.length) return; + // let filename = fh.getFileBase(merge.filename) + '.seq', + // filePath = `${ merge.dataPath}\\seq\\${ filename}`, + // buffer = new Buffer(formIds.length * 4); + // formIds.forEach((fid, n) => buffer.writeUInt32LE(fid, n * 4)); + // fh.jetpack.write(filePath, buffer); + // progressLogger.log('Created SEQ file: ' + filePath); + } + + private List GetSeqQuests(TMod merge) + { + var masterColl = MasterReferenceCollection.FromPath( + Path.Combine(_outputDir, _mergeName), + _mergeState.Release, + _fileSystem); + + IGroup quests = _mergeState.Release switch + { + GameRelease.Oblivion => merge.GetTopLevelGroup(), + GameRelease.Fallout4 => merge.GetTopLevelGroup(), + _ => merge.GetTopLevelGroup(), + }; + + List formIds = new List(); + + if (quests.Count == 0) return formIds; + + Parallel.ForEach(quests.Records, quest => { + bool startGameEnabled = _mergeState.Release switch { + GameRelease.Oblivion => ((Oblivion.Quest)quest).Data?.Flags.HasFlag(Oblivion.Quest.Flag.StartGameEnabled) ?? false, + GameRelease.Fallout4 => ((Fallout4.Quest)quest).Data?.Flags.HasFlag(Fallout4.Quest.Flag.StartGameEnabled) ?? false, + _ => ((Skyrim.Quest)quest).Flags.HasFlag(Skyrim.Quest.Flag.StartGameEnabled) + }; + if (startGameEnabled) + { + throw new NotImplementedException(); + // This got hidden in latest mutagen after Starfield flipped the table. + // Will need to re-expose how FormIDs are generated in a tool the public has access to. + // var fid = masterColl.GetFormID(quest.FormKey).Raw; + // formIds.Add(fid); + } + }); + + return formIds; + } + + private void CopyAssets(DirectoryPath path, ModKey mod) + { + CopyActorAssets(path, "textures/actors/character/facegendata/facetint", mod); + CopyActorAssets(path, "meshes/actors/character/facegendata/facegeom", mod); + CopyActorAssets(path, "sound/voice", mod); + + CopyTranslations(path, mod); + } + + private void ExtractBSA(string bsa, DirectoryPath temp) + { + Console.WriteLine(); + + var reader = Archive.CreateReader(_mergeState.Release, bsa); + var files = reader.Files.ToArray(); + Parallel.For(0,files.Count(), i => { + var file = files[i]; + var filePath = file.Path.Replace("\\", "/").ToLower(); + _fileSystem.Directory.CreateDirectory(Path.Combine(temp, Path.GetDirectoryName(filePath) ?? "")); + Console.SetCursorPosition(0, Console.CursorTop); + Console.Write(" Extracting Archive \"" + Path.GetFileName(bsa) + "\" " + ((decimal)i / files.Count()).ToString("0.00%")); + File.WriteAllBytes(Path.Combine(temp, filePath), file.GetBytes()); + }); + Console.SetCursorPosition(0, Console.CursorTop); + Console.Write(" Extracting Archive \"" + Path.GetFileName(bsa) + "\" 100.00%"); + } + + private void CopyTranslations(string dir, ModKey mod) + { + var path = "interface/translations/"; + var srcPath = Path.Combine(dir, path); + var dstPath = Path.Combine(_outputDir, path); + + if (!_fileSystem.Directory.Exists(srcPath)) return; + + foreach (var file in _fileSystem.Directory.GetFiles(srcPath, + mod.Name.ToLower() + "_*.txt", + new EnumerationOptions() { RecurseSubdirectories = true, MatchCasing = MatchCasing.CaseInsensitive })) + { + var language = Path.GetFileNameWithoutExtension(file).Replace(mod.Name.ToLower() + "_", ""); + var dst = dstPath + _mergeName.Substring(0, _mergeName.Length - 4) + "_" + language + ".txt"; + _fileSystem.Directory.CreateDirectory(dstPath); + + var writer = _fileSystem.File.AppendText(dst); + writer.Write(_fileSystem.File.ReadAllText(file)); + writer.Close(); + + Console.WriteLine(" Appending " + mod.Name.ToLower() + "_" + "language to " + _mergeName.Substring(0, _mergeName.Length - 4) + "_" + language); + + + }; + } + + private void CopyActorAssets(string dir, string _path, ModKey mod) + { + var path = _path.Replace("\\", "/"); + + var srcPath = Path.Combine(dir, path, mod.FileName.String.ToLower()); + var dstPath = Path.Combine(_outputDir, path, _mergeName); + _fileSystem.Directory.CreateDirectory(dstPath); + + if (!_fileSystem.Directory.Exists(srcPath)) return; + + Console.WriteLine(" Copying assets from directory \"" + Path.Combine(path, mod.FileName.String.ToLower()) + "\""); + Console.WriteLine(" Copying assets to directory \"" + Path.Combine(path, _mergeName) + "\""); + + _mergeState.Mapping.Where(x => x.Key.ModKey == mod).ForEach(x => + { + var srcId = x.Key.ID; + var srcIdString = x.Key.IDString().ToLower(); + + foreach (var file in _fileSystem.Directory.GetFiles(srcPath, + "*" + srcIdString + "*", + new EnumerationOptions() { RecurseSubdirectories = true, MatchCasing = MatchCasing.CaseInsensitive })) + { + if (_mergeState.Mapping.Select(x => x.Key.ModKey).Contains(mod) && _mergeState.Mapping.Select(x => x.Key.ID).Contains(srcId)) + { + var newId = "00" + _mergeState.Mapping[new FormKey(mod, srcId)].IDString().ToLower(); + var dstFile = file.Replace(srcIdString, newId).Replace(srcPath, dstPath); + _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(dstFile) ?? ""); + Console.WriteLine(" Asset renumbered from " + srcIdString + " to " + newId); + Console.WriteLine(" Copying asset \"" + file.Replace(srcPath + "/", "") + "\" to \"" + dstFile.Replace(dstPath + "/", "") + "\""); + _fileSystem.File.Copy(file, dstFile); + } + else + { + _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(file.Replace(srcPath, dstPath)) ?? ""); + Console.WriteLine(" Asset not renumbered."); + Console.WriteLine(" Copying asset \"" + file.Replace(srcPath + "/", "") + "\""); + _fileSystem.File.Copy(file, file.Replace(srcPath, dstPath)); + + } + + } + }); + } +} diff --git a/MutagenMerger.Lib/DI/CopyRecordProcessor.cs b/Mutagen.Bethesda.Merge.Lib/DI/CopyRecordProcessor.cs similarity index 89% rename from MutagenMerger.Lib/DI/CopyRecordProcessor.cs rename to Mutagen.Bethesda.Merge.Lib/DI/CopyRecordProcessor.cs index 5e20a16..16b21f5 100644 --- a/MutagenMerger.Lib/DI/CopyRecordProcessor.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/CopyRecordProcessor.cs @@ -1,40 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Loqui; -using Mutagen.Bethesda; +using Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; +using Noggog; using SkyrimRecord = Mutagen.Bethesda.Skyrim; using Fallout4Record = Mutagen.Bethesda.Fallout4; -using OblivionRecord = Mutagen.Bethesda.Oblivion; -using MutagenMerger.Lib.DI.GameSpecifications; -using Noggog; -namespace MutagenMerger.Lib.DI; +namespace Mutagen.Bethesda.Merge.Lib.DI; public class CopyRecordProcessor where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter { - private readonly Dictionary> _copyOverrides; + private readonly Dictionary> _copyOverrides; public CopyRecordProcessor(ICopyOverride[] copyOverrides) { _copyOverrides = copyOverrides - // .GroupBy(x => x.ObjectKey) + // .GroupBy(x => x.ProtocolKey) // .ToDictionary(x => x.Key, x => x.First()); - .ToDictionary(x => x.ObjectKey, x => x); + .ToDictionary(x => x.ClassType, x => x); } public void CopyRecords( MergeState mergeState) { foreach (var rec in mergeState.Mods.Where(mod => mergeState.ModsToMerge.Contains(mod.ModKey)) - .WinningOverrideContexts(mergeState + .WinningContextOverrides(mergeState .LinkCache)) { - if (_copyOverrides.TryGetValue(rec.Record.Registration.ObjectKey, out var copyOverride)) + if (_copyOverrides.TryGetValue(rec.Record.Registration.ClassType, out var copyOverride)) { copyOverride.HandleCopyFor(mergeState, rec); } @@ -66,12 +60,13 @@ private void DuplicateAsNewRecord( Console.WriteLine(" Renumbering Record [" + rec.Record.FormKey.ModKey.Name + "] " + rec.Record.FormKey.IDString() + " to [" + mergeState.OutgoingMod.ModKey.Name + "] " + duplicated.FormKey.IDString()); - mergeState.Mapping.Add(rec.Record.FormKey, duplicated.FormKey); } else { Console.WriteLine(" Copying Record [" + rec.Record.FormKey.ModKey.Name + "] " + rec.Record.FormKey.IDString()); } + + mergeState.Mapping.Add(rec.Record.FormKey, duplicated.FormKey); } private static MajorRecord DuplicateAndRenumber(MergeState mergeState, IModContext rec) @@ -86,6 +81,7 @@ private static MajorRecord DuplicateAndRenumber(MergeState mer type = typeof(SkyrimRecord.Global); } var group = mergeState.OutgoingMod.GetTopLevelGroup(type); + Console.WriteLine(group.ToString()); group.AddUntyped(duplicated); return duplicated; } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs similarity index 99% rename from MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs index c23f424..c3b9702 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.Generated.cs @@ -7,7 +7,7 @@ using Mutagen.Bethesda.Fallout4; using Mutagen.Bethesda.Oblivion; -namespace MutagenMerger.Lib.DI.GameSpecifications; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications; diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.tt b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.tt similarity index 97% rename from MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.tt rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.tt index de9f218..3e30a67 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Base/BlankOverride.tt +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/BlankOverride.tt @@ -8,7 +8,7 @@ using Mutagen.Bethesda.Skyrim; using Mutagen.Bethesda.Fallout4; using Mutagen.Bethesda.Oblivion; -namespace MutagenMerger.Lib.DI.GameSpecifications; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications; <# var placedVariants = new string[][] { new string[] {"Skyrim","PlacedObject"}, diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Base/CellOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/CellOverride.cs similarity index 89% rename from MutagenMerger.Lib/DI/GameSpecifications/Base/CellOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/CellOverride.cs index afd1d56..2010c1a 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Base/CellOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Base/CellOverride.cs @@ -1,21 +1,18 @@ -using System; -using Mutagen.Bethesda; +using Mutagen.Bethesda.Fallout4; +using Mutagen.Bethesda.Oblivion; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; +using Mutagen.Bethesda.Skyrim; using SkyrimRecord = Mutagen.Bethesda.Skyrim; using Fallout4Record = Mutagen.Bethesda.Fallout4; using OblivionRecord = Mutagen.Bethesda.Oblivion; -using System.Collections.Generic; -using Mutagen.Bethesda.Oblivion; -using System.Linq; -using Mutagen.Bethesda.Fallout4; -using Mutagen.Bethesda.Skyrim; -namespace MutagenMerger.Lib.DI.GameSpecifications.Base; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Base; public class CellOverride { - public static IMajorRecord CopyCellAsOverride(MergeState state, + public static IMajorRecord CopyCellAsOverride( + MergeState state, IModContext context) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter @@ -52,9 +49,10 @@ public static IMajorRecord CopyCellAsOverride(MergeState state, - IModContext context, MajorRecord.TranslationMask mask) - + public static IMajorRecord DuplicateCell( + MergeState state, + IModContext context, + MajorRecord.TranslationMask mask) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter @@ -73,7 +71,6 @@ public static bool RecordExists( MergeState state, IMajorRecord newRecord, IMajorRecordGetter temp) - where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter { @@ -98,9 +95,10 @@ public static bool RecordExists( } - public static void CopySubRecords(MergeState state, IModContext context, IMajorRecord newRecord) - - + public static void CopySubRecords( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter @@ -132,9 +130,10 @@ public static void CopySubRecords(MergeState state, IModContext context, IMajorRecord newRecord) - - + private static void CopyNavmesh( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter @@ -172,9 +171,10 @@ private static void CopyNavmesh(MergeState state, IModContext context, IMajorRecord newRecord) - - + private static void CopyLandscape( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter @@ -221,13 +221,12 @@ private static void CopyLandscape(MergeState state, IModContext context, IMajorRecord newRecord) - - + + private static void CopyPathing( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter @@ -262,20 +261,17 @@ private static void CopyPathing(MergeState state, IModContext context, IMajorRecord newRecord) - - + + private static void CopyPersistent( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter where TMajorRecordGetter : class, IMajorRecordGetter { - IReadOnlyList list = state.Release == GameRelease.Oblivion ? ((OblivionRecord.ICellGetter)context.Record).Persistent : state.Release == GameRelease.Fallout4 ? ((Fallout4Record.ICellGetter)context.Record).Persistent : @@ -296,8 +292,7 @@ private static void CopyPersistent(MergeState state, IModContext context, IMajorRecord newRecord) - - + + private static void CopyTemporary( + MergeState state, + IModContext context, + IMajorRecord newRecord) where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter where TMajorRecord : class, IMajorRecord, TMajorRecordGetter where TMajorRecordGetter : class, IMajorRecordGetter { - IReadOnlyList list = state.Release == GameRelease.Oblivion ? ((OblivionRecord.ICellGetter)context.Record).Temporary : state.Release == GameRelease.Fallout4 ? ((Fallout4Record.ICellGetter)context.Record).Temporary : @@ -364,15 +357,16 @@ private static void CopyTemporary(MergeState state, IModContext context, IMajorRecord newRecord) - - where TModGetter : class, IModGetter, IContextGetterMod - where TMod : class, IMod, IContextMod, TModGetter - where TMajorRecord : class, IMajorRecord, TMajorRecordGetter - where TMajorRecordGetter : class, IMajorRecordGetter + private static void CopyDistance( + MergeState state, + IModContext context, + IMajorRecord newRecord) + where TModGetter : class, IModGetter, IContextGetterMod + where TMod : class, IMod, IContextMod, TModGetter + where TMajorRecord : class, IMajorRecord, TMajorRecordGetter + where TMajorRecordGetter : class, IMajorRecordGetter { - IReadOnlyList list = ((OblivionRecord.ICellGetter)context.Record).VisibleWhenDistant; foreach (var vis in list) { @@ -386,18 +380,13 @@ private static void CopyDistance(MergeStat public static IMajorRecord DuplicateDialogTopic(MergeState state, IMajorRecordGetter record, MajorRecord.TranslationMask mask) - where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter { // Don't duplicate branches, as they will be added below IMajorRecord newRecord = record.Duplicate(state.GetFormKey(record.FormKey),mask); - - + switch (state.Release) { case GameRelease.Oblivion: diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs similarity index 64% rename from MutagenMerger.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs index 2d5a172..98fc84c 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/CellOverride.cs @@ -1,14 +1,12 @@ -using System; -using Mutagen.Bethesda; +using Mutagen.Bethesda.Fallout4; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; -using Mutagen.Bethesda.Fallout4; -namespace MutagenMerger.Lib.DI.GameSpecifications.Fallout4; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Fallout4; public class CellOverride : ACopyOverride { - public static readonly Cell.TranslationMask CellMask = new Mutagen.Bethesda.Fallout4.Cell.TranslationMask(defaultOn: true) + private static readonly Cell.TranslationMask CellMask = new(defaultOn: true) { Persistent = false, Temporary = false, @@ -21,26 +19,23 @@ public class CellOverride : ACopyOverride state, IModContext context) { - - Mutagen.Bethesda.Fallout4.Cell? newRecord; - - + IMajorRecord? newRecord; + if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Fallout4.Cell)Base.CellOverride.CopyCellAsOverride(state, context); - + newRecord = Base.CellOverride.CopyCellAsOverride(state, context); } else { // Don't duplicate branches, as they will be added below - newRecord = (Mutagen.Bethesda.Fallout4.Cell)Base.CellOverride.DuplicateCell(state, context, CellMask); + newRecord = Base.CellOverride.DuplicateCell(state, context, CellMask); } Base.CellOverride.CopySubRecords(state, context, newRecord); } - } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs similarity index 76% rename from MutagenMerger.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs index a799f58..eb66fb8 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/Fallout4Specifications.cs @@ -1,6 +1,6 @@ using Mutagen.Bethesda.Fallout4; -namespace MutagenMerger.Lib.DI.GameSpecifications.Fallout4; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Fallout4; public class Fallout4Specifications : IGameSpecifications { diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs similarity index 79% rename from MutagenMerger.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs index c28cc06..cb16af7 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/QuestOverride.cs @@ -1,33 +1,30 @@ -using System; -using Mutagen.Bethesda; +using Mutagen.Bethesda.Fallout4; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; -using Mutagen.Bethesda.Fallout4; -namespace MutagenMerger.Lib.DI.GameSpecifications.Fallout4; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Fallout4; public class QuestOverride : ACopyOverride { - public static readonly Quest.TranslationMask QuestMask = new Quest.TranslationMask(defaultOn: true) + private static readonly Quest.TranslationMask QuestMask = new(defaultOn: true) { DialogTopics = false }; - public static readonly DialogTopic.TranslationMask DialogTopicMask = new DialogTopic.TranslationMask(defaultOn: true) + + private static readonly DialogTopic.TranslationMask DialogTopicMask = new(defaultOn: true) { Responses = false }; + public override void HandleCopyFor( MergeState state, IModContext context) { - IQuest? newRecord; - - + if (state.IsOverride(context.Record.FormKey, context.ModKey)) { newRecord = context.GetOrAddAsOverride(state.OutgoingMod); - } else { @@ -50,20 +47,18 @@ public override void HandleCopyFor( { Base.DialogTopicOverride.CopyDialogResponses(state, context.ModKey, newTopic, response); } - } foreach (var branch in context.Record.DialogBranches) { - DialogBranch newBranch; if (state.IsOverride(branch.FormKey, context.ModKey)) { - newBranch = (DialogBranch)branch.DeepCopy(); + newBranch = branch.DeepCopy(); } else { - newBranch = (DialogBranch)branch.Duplicate(state.GetFormKey(branch.FormKey)); + newBranch = branch.Duplicate(state.GetFormKey(branch.FormKey)); } state.Mapping.Add(branch.FormKey, newBranch.FormKey); @@ -72,5 +67,4 @@ public override void HandleCopyFor( Console.WriteLine(" Deep Copying [" + branch.FormKey.ModKey.Name + "] " + branch.FormKey.IDString() + " to [" + newBranch.FormKey.ModKey.Name + "] " + newBranch.FormKey.IDString()); } } - } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs similarity index 74% rename from MutagenMerger.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs index 7d234aa..917976f 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Fallout4/WorldspaceOverride.cs @@ -1,12 +1,11 @@ -using System; +using Mutagen.Bethesda.Fallout4; using Mutagen.Bethesda.Plugins.Cache; -using Mutagen.Bethesda.Fallout4; -namespace MutagenMerger.Lib.DI.GameSpecifications.Fallout4; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Fallout4; public class WorldspaceOverride : ACopyOverride { - private static readonly Worldspace.TranslationMask WorldspaceMask = new Mutagen.Bethesda.Fallout4.Worldspace.TranslationMask(defaultOn: true) + private static readonly Worldspace.TranslationMask WorldspaceMask = new(defaultOn: true) { SubCells = false, TopCell = false, @@ -20,10 +19,9 @@ public override void HandleCopyFor( MergeState state, IModContext context) { - Mutagen.Bethesda.Fallout4.Worldspace newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Fallout4.Worldspace)context.GetOrAddAsOverride(state.OutgoingMod); + var newRecord = (Mutagen.Bethesda.Fallout4.Worldspace)context.GetOrAddAsOverride(state.OutgoingMod); // Readd branches below newRecord.LargeReferences.Clear(); newRecord.SubCells.Clear(); @@ -34,7 +32,7 @@ public override void HandleCopyFor( else { // Don't duplicate branches, as they will be added below - newRecord = (Mutagen.Bethesda.Fallout4.Worldspace)context.Record.Duplicate(state.GetFormKey(context.Record.FormKey)); + var newRecord = (Mutagen.Bethesda.Fallout4.Worldspace)context.Record.Duplicate(state.GetFormKey(context.Record.FormKey)); state.OutgoingMod.Worldspaces.Add(newRecord); @@ -42,8 +40,5 @@ public override void HandleCopyFor( Console.WriteLine(" Deep Copying [" + context.Record.FormKey.ModKey.Name + "] " + context.Record.FormKey.IDString() + " to [" + newRecord.FormKey.ModKey.Name + "] " + newRecord.FormKey.IDString()); } - - - - } + } } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/ICopyOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/ICopyOverride.cs similarity index 85% rename from MutagenMerger.Lib/DI/GameSpecifications/ICopyOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/ICopyOverride.cs index 1cf9ea9..04d9e39 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/ICopyOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/ICopyOverride.cs @@ -1,15 +1,14 @@ using Loqui; -using Mutagen.Bethesda; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; -namespace MutagenMerger.Lib.DI.GameSpecifications; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications; public interface ICopyOverride where TModGetter : class, IModGetter, IContextGetterMod where TMod : class, IMod, IContextMod, TModGetter { - ObjectKey ObjectKey { get; } + Type ClassType { get; } public void HandleCopyFor( MergeState state, IModContext context); @@ -21,7 +20,7 @@ public abstract class ACopyOverride state, diff --git a/MutagenMerger.Lib/DI/GameSpecifications/IGameSpecifications.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/IGameSpecifications.cs similarity index 89% rename from MutagenMerger.Lib/DI/GameSpecifications/IGameSpecifications.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/IGameSpecifications.cs index dd56e85..8f07bc5 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/IGameSpecifications.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/IGameSpecifications.cs @@ -1,6 +1,6 @@ using Mutagen.Bethesda.Plugins.Records; -namespace MutagenMerger.Lib.DI.GameSpecifications; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications; public interface IGameSpecifications where TModGetter : class, IModGetter, IContextGetterMod diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs similarity index 62% rename from MutagenMerger.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs index c9de5f3..1721c82 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/CellOverride.cs @@ -1,14 +1,12 @@ -using System; -using Mutagen.Bethesda; +using Mutagen.Bethesda.Oblivion; using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; -using Mutagen.Bethesda.Oblivion; -namespace MutagenMerger.Lib.DI.GameSpecifications.Oblivion; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Oblivion; public class CellOverride : ACopyOverride { - public static readonly Cell.TranslationMask CellMask = new Mutagen.Bethesda.Oblivion.Cell.TranslationMask(defaultOn: true) + public static readonly Cell.TranslationMask CellMask = new(defaultOn: true) { Persistent = false, Temporary = false, @@ -19,26 +17,23 @@ public class CellOverride : ACopyOverride state, IModContext context) { - - Mutagen.Bethesda.Oblivion.Cell? newRecord; - + IMajorRecord? newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Oblivion.Cell)Base.CellOverride.CopyCellAsOverride(state, context); - + newRecord = Base.CellOverride.CopyCellAsOverride(state, context); } else { // Don't duplicate branches, as they will be added below - newRecord = (Mutagen.Bethesda.Oblivion.Cell)Base.CellOverride.DuplicateCell(state, context, CellMask); + newRecord = Base.CellOverride.DuplicateCell(state, context, CellMask); } Base.CellOverride.CopySubRecords(state, context, newRecord); } - } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs similarity index 84% rename from MutagenMerger.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs index d064064..eb7165c 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/DialogTopicOverride.cs @@ -1,22 +1,22 @@ -using System; +using Mutagen.Bethesda.Oblivion; using Mutagen.Bethesda.Plugins; using Mutagen.Bethesda.Plugins.Cache; -using Mutagen.Bethesda.Oblivion; using Mutagen.Bethesda.Plugins.Records; -namespace MutagenMerger.Lib.DI.GameSpecifications.Oblivion; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Oblivion; public class DialogTopicOverride : ACopyOverride { - private static readonly DialogTopic.TranslationMask DialogTopicMask = new Mutagen.Bethesda.Oblivion.DialogTopic.TranslationMask(defaultOn: true) + private static readonly DialogTopic.TranslationMask DialogTopicMask = new(defaultOn: true) { Items = false }; + public override void HandleCopyFor( MergeState state, IModContext context) { - Mutagen.Bethesda.Oblivion.DialogTopic newRecord; + DialogTopic newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { newRecord = (DialogTopic)Base.DialogTopicOverride.CopyDialogTopicAsOverride(state, (IMajorRecordGetter)context.Record); @@ -32,15 +32,14 @@ public override void HandleCopyFor( CopyDialogItem(state, context.ModKey, newRecord, branch); } } - - + private void CopyDialogItem( MergeState state, ModKey currentMod, - Mutagen.Bethesda.Oblivion.DialogTopic topic, + DialogTopic topic, IDialogItemGetter item) { - Mutagen.Bethesda.Oblivion.DialogItem newRecord; + DialogItem newRecord; if (state.IsOverride(item.FormKey, currentMod)) { newRecord = item.DeepCopy(); diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs similarity index 76% rename from MutagenMerger.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs index 10b618d..2d21b1e 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/OblivionSpecifications.cs @@ -1,6 +1,6 @@ using Mutagen.Bethesda.Oblivion; -namespace MutagenMerger.Lib.DI.GameSpecifications.Oblivion; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Oblivion; public class OblivionSpecifications : IGameSpecifications { diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs similarity index 80% rename from MutagenMerger.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs index 29cab00..81cb205 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Oblivion/WorldspaceOverride.cs @@ -1,12 +1,11 @@ -using System; +using Mutagen.Bethesda.Oblivion; using Mutagen.Bethesda.Plugins.Cache; -using Mutagen.Bethesda.Oblivion; -namespace MutagenMerger.Lib.DI.GameSpecifications.Oblivion; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Oblivion; public class WorldspaceOverride : ACopyOverride { - private static readonly Worldspace.TranslationMask WorldspaceMask = new Mutagen.Bethesda.Oblivion.Worldspace.TranslationMask(defaultOn: true) + private static readonly Worldspace.TranslationMask WorldspaceMask = new(defaultOn: true) { SubCells = false, TopCell = false, @@ -18,10 +17,10 @@ public override void HandleCopyFor( MergeState state, IModContext context) { - Mutagen.Bethesda.Oblivion.Worldspace newRecord; + Worldspace newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Oblivion.Worldspace)context.GetOrAddAsOverride(state.OutgoingMod); + newRecord = (Worldspace)context.GetOrAddAsOverride(state.OutgoingMod); // Readd branches below newRecord.SubCells.Clear(); newRecord.TopCell?.Clear(); @@ -39,8 +38,5 @@ public override void HandleCopyFor( Console.WriteLine(" Deep Copying [" + context.Record.FormKey.ModKey.Name + "] " + context.Record.FormKey.IDString() + " to [" + newRecord.FormKey.ModKey.Name + "] " + newRecord.FormKey.IDString()); } - - - - } + } } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs similarity index 64% rename from MutagenMerger.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs index e88491d..b491d3d 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/CellOverride.cs @@ -1,14 +1,12 @@ -using System; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Plugins.Cache; +using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; using Mutagen.Bethesda.Skyrim; -namespace MutagenMerger.Lib.DI.GameSpecifications.Skyrim; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Skyrim; public class CellOverride : ACopyOverride { - public static readonly Cell.TranslationMask CellMask = new Mutagen.Bethesda.Skyrim.Cell.TranslationMask(defaultOn: true) + public static readonly Cell.TranslationMask CellMask = new(defaultOn: true) { Persistent = false, Temporary = false, @@ -21,26 +19,23 @@ public class CellOverride : ACopyOverride state, IModContext context) { - - Mutagen.Bethesda.Skyrim.Cell? newRecord; - - + IMajorRecord? newRecord; + if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Skyrim.Cell)Base.CellOverride.CopyCellAsOverride(state, context); - + newRecord = Base.CellOverride.CopyCellAsOverride(state, context); } else { // Don't duplicate branches, as they will be added below - newRecord = (Mutagen.Bethesda.Skyrim.Cell)Base.CellOverride.DuplicateCell(state, context, CellMask); + newRecord = Base.CellOverride.DuplicateCell(state, context, CellMask); } Base.CellOverride.CopySubRecords(state, context, newRecord); } - } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs similarity index 59% rename from MutagenMerger.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs index d408960..70676b6 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/DialogTopicOverride.cs @@ -1,14 +1,12 @@ -using System; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Plugins.Cache; +using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Plugins.Records; using Mutagen.Bethesda.Skyrim; -namespace MutagenMerger.Lib.DI.GameSpecifications.Skyrim; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Skyrim; public class DialogTopicOverride : ACopyOverride { - private static readonly DialogTopic.TranslationMask DialogTopicMask = new Mutagen.Bethesda.Skyrim.DialogTopic.TranslationMask(defaultOn: true) + private static readonly DialogTopic.TranslationMask DialogTopicMask = new(defaultOn: true) { Responses = false }; @@ -17,14 +15,14 @@ public override void HandleCopyFor( MergeState state, IModContext context) { - Mutagen.Bethesda.Skyrim.DialogTopic newRecord; + IMajorRecord newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (DialogTopic)Base.DialogTopicOverride.CopyDialogTopicAsOverride(state, (IMajorRecordGetter)context.Record); + newRecord = Base.DialogTopicOverride.CopyDialogTopicAsOverride(state, (IMajorRecordGetter)context.Record); } else { - newRecord = (DialogTopic)Base.DialogTopicOverride.DuplicateDialogTopic(state, (IMajorRecordGetter)context.Record, DialogTopicMask); + newRecord = Base.DialogTopicOverride.DuplicateDialogTopic(state, (IMajorRecordGetter)context.Record, DialogTopicMask); } // Do the branches @@ -33,5 +31,4 @@ public override void HandleCopyFor( Base.DialogTopicOverride.CopyDialogResponses(state, context.ModKey, newRecord, response); } } - } diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs similarity index 75% rename from MutagenMerger.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs index 6cf3cb9..4687b72 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/SkyrimSpecifications.cs @@ -1,6 +1,6 @@ using Mutagen.Bethesda.Skyrim; -namespace MutagenMerger.Lib.DI.GameSpecifications.Skyrim; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Skyrim; public class SkyrimSpecifications : IGameSpecifications { diff --git a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs similarity index 74% rename from MutagenMerger.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs rename to Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs index d9c9ad2..ead6516 100644 --- a/MutagenMerger.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs +++ b/Mutagen.Bethesda.Merge.Lib/DI/GameSpecifications/Skyrim/WorldspaceOverride.cs @@ -1,12 +1,11 @@ -using System; -using Mutagen.Bethesda.Plugins.Cache; +using Mutagen.Bethesda.Plugins.Cache; using Mutagen.Bethesda.Skyrim; -namespace MutagenMerger.Lib.DI.GameSpecifications.Skyrim; +namespace Mutagen.Bethesda.Merge.Lib.DI.GameSpecifications.Skyrim; public class WorldspaceOverride : ACopyOverride { - private static readonly Worldspace.TranslationMask WorldspaceMask = new Mutagen.Bethesda.Skyrim.Worldspace.TranslationMask(defaultOn: true) + private static readonly Worldspace.TranslationMask WorldspaceMask = new(defaultOn: true) { SubCells = false, TopCell = false, @@ -20,10 +19,9 @@ public override void HandleCopyFor( MergeState state, IModContext context) { - Mutagen.Bethesda.Skyrim.Worldspace newRecord; if (state.IsOverride(context.Record.FormKey, context.ModKey)) { - newRecord = (Mutagen.Bethesda.Skyrim.Worldspace)context.GetOrAddAsOverride(state.OutgoingMod); + var newRecord = context.GetOrAddAsOverride(state.OutgoingMod); // Readd branches below newRecord.LargeReferences.Clear(); newRecord.SubCells.Clear(); @@ -34,7 +32,7 @@ public override void HandleCopyFor( else { // Don't duplicate branches, as they will be added below - newRecord = (Mutagen.Bethesda.Skyrim.Worldspace)context.Record.Duplicate(state.GetFormKey(context.Record.FormKey), WorldspaceMask); + var newRecord = context.Record.Duplicate(state.GetFormKey(context.Record.FormKey), WorldspaceMask); state.OutgoingMod.Worldspaces.Add(newRecord); @@ -42,8 +40,5 @@ public override void HandleCopyFor( Console.WriteLine(" Deep Copying [" + context.Record.FormKey.ModKey.Name + "] " + context.Record.FormKey.IDString() + " to [" + newRecord.FormKey.ModKey.Name + "] " + newRecord.FormKey.IDString()); } - - - - } + } } diff --git a/Mutagen.Bethesda.Merge.Lib/DI/Merger.cs b/Mutagen.Bethesda.Merge.Lib/DI/Merger.cs new file mode 100644 index 0000000..3e55907 --- /dev/null +++ b/Mutagen.Bethesda.Merge.Lib/DI/Merger.cs @@ -0,0 +1,206 @@ +using System.IO.Abstractions; +using System.Json; +using System.Security.Cryptography; +using Mutagen.Bethesda.Environments; +using Mutagen.Bethesda.Environments.DI; +using Mutagen.Bethesda.Plugins; +using Mutagen.Bethesda.Plugins.Order.DI; +using Mutagen.Bethesda.Plugins.Records; +using Noggog; + +namespace Mutagen.Bethesda.Merge.Lib.DI; + +public interface IMerger +{ + void Merge( + IEnumerable modsToMerge, + ModKey outputKey, + DirectoryPath outputFolder); +} + +public sealed class Merger : IMerger + where TMod : class, TModGetter, IMod, IContextMod + where TModGetter : class, IModGetter, IContextGetterMod + where TMajorRecord : class, TMajorRecordGetter, IMajorRecord + where TMajorRecordGetter : class, IMajorRecordGetter +{ + private readonly IFileSystem _fileSystem; + private readonly IDataDirectoryProvider _dataDirectoryProvider; + private readonly IGameReleaseContext _gameReleaseContext; + private readonly ILoadOrderListingsProvider _loadOrderListingsProvider; + private readonly AssetMerge.Factory _assetMergeFactory; + private readonly CopyRecordProcessor _copyRecordProcessor; + private readonly MD5 _md5; + + public Merger( + IFileSystem fileSystem, + IDataDirectoryProvider dataDirectoryProvider, + IGameReleaseContext gameReleaseContext, + ILoadOrderListingsProvider loadOrderListingsProvider, + AssetMerge.Factory assetMergeFactory, + CopyRecordProcessor copyRecordProcessor) + { + _fileSystem = fileSystem; + _dataDirectoryProvider = dataDirectoryProvider; + _gameReleaseContext = gameReleaseContext; + _loadOrderListingsProvider = loadOrderListingsProvider; + _assetMergeFactory = assetMergeFactory; + _copyRecordProcessor = copyRecordProcessor; + _md5 = MD5.Create(); + } + + public void Merge( + IEnumerable modsToMerge, + ModKey outputKey, + DirectoryPath outputFolder) + { + var outputMod = ModInstantiator.Activator(outputKey, _gameReleaseContext.Release); + var env = GameEnvironmentBuilder + .Create(_gameReleaseContext.Release) + .WithTargetDataFolder(_dataDirectoryProvider.Path) + .WithOutputMod(outputMod) + .WithLoadOrder(_loadOrderListingsProvider.Get() + .Where(x => x.Enabled) + .Select(x => x.ModKey) + .ToArray()) + .WithFileSystem(_fileSystem) + .Build(); + var mods = env.LoadOrder.PriorityOrder.ResolveAllModsExist().ToArray(); + var mergingMods = mods.Where(x => modsToMerge.Contains(x.ModKey)).ToArray(); + + var outputFile = Path.Combine(outputFolder, outputKey.FileName); + // env.LoadOrder.ForEach(x => Console.WriteLine(x.Value.ModKey)); + + Console.WriteLine("Merging " + String.Join(", ",mergingMods.Select(x => x.ModKey.FileName.String)) + " into " + Path.GetFileName(outputFile)); + Console.WriteLine(); + + var modsToMergeSet = modsToMerge.ToHashSet(); + + var linkCache = mergingMods.ToImmutableLinkCache(); + + var state = new MergeState( + _gameReleaseContext.Release, + mods, + modsToMergeSet, + outputMod, + OutputPath: outputFile, + DataPath: _dataDirectoryProvider.Path, + LinkCache: linkCache, + env: env); + + _copyRecordProcessor.CopyRecords(state); + // state.Mapping.ForEach(x => Console.WriteLine(x.Key.ToString() + " " + x.Value.ToString())); + state.OutgoingMod.RemapLinks(state.Mapping); + + _fileSystem.Directory.CreateDirectory(state.OutputPath.Directory ?? ""); + state.OutgoingMod.BeginWrite + .ToPath(state.OutputPath) + .WithLoadOrder(env.LoadOrder.Keys) + .WithDataFolder(env.DataFolderPath) + .WithFileSystem(_fileSystem) + .Write(); + + // foreach (var rec in state.OutgoingMod.EnumerateMajorRecords()) + // { + // foreach (var link in rec.EnumerateFormLinks()) + // { + // IMajorRecordGetter? majorRecord = null; + // link.TryResolveCommon(state.LinkCache, out majorRecord); + // Console.WriteLine(majorRecord?.ToString() + ":" +link.FormKey); + // } + // } + + HandleAssets(state); + + HandleScripts(state); + + MergeJson(state); + } + + private void MergeJson(MergeState state) + { + var outputDir = Path.GetDirectoryName(state.OutputPath) ?? ""; + var mergeName = Path.GetFileNameWithoutExtension(state.OutputPath); + var mergePlugin = state.OutgoingMod.ModKey.FileName; + var mergeDir = Path.Combine(outputDir, "merge - " + mergeName); + if (!_fileSystem.Directory.Exists(mergeDir)) + { + _fileSystem.Directory.CreateDirectory(mergeDir); + } + + JsonObject? _mergeJson = new() + { + { "name", new JsonPrimitive(mergeName) }, + { "filename", new JsonPrimitive(mergePlugin)}, + { "method", new JsonPrimitive("Mutagen.Bethesda.Merge")}, + { "loadOrder", new JsonArray ( + state.env.LoadOrder.PriorityOrder + .Select(x => x.ModKey.FileName) + .Select(x => new JsonPrimitive(x)) + .Select(x => (JsonValue)x) + .ToArray() + )}, + {"dateBuilt", new JsonPrimitive(DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.zzZ"))}, + {"plugins", new JsonArray ( + state.ModsToMerge + .Select(x => + { + return new JsonObject + { + { "filename", new JsonPrimitive(x.FileName) }, + { "hash", new JsonPrimitive(BitConverter.ToString(_md5.ComputeHash(_fileSystem.File.ReadAllBytes(Path.Combine(state.env.DataFolderPath,x.FileName)))).Replace("-", "").ToLowerInvariant()) }, + { "dataFolder", new JsonPrimitive(state.env.DataFolderPath)} + }; + }) + .Select(x => (JsonValue)x) + .ToArray() + )} + }; + + _fileSystem.File.WriteAllText(Path.Combine(mergeDir, "merge.json"), _mergeJson.ToString()); + + JsonObject? mapJson = new( + state.ModsToMerge.Select( + x => new KeyValuePair( + x.FileName, + new JsonObject(state.Mapping.Where(y => y.Key.ModKey.Equals(x) && y.Key.ID != y.Value.ID) + .Select( + y => new KeyValuePair( + y.Key.IDString(), + new JsonPrimitive(y.Value.IDString()) + ) + ).ToArray())) + ) + ); + + _fileSystem.File.WriteAllText(Path.Combine(mergeDir, "map.json"), mapJson.ToString()); + + JsonObject? fidJson = new( + state.ModsToMerge.Select( + x => new KeyValuePair( + x.FileName, + new JsonArray( + state.Mapping + .Where(y => y.Key.ModKey.Equals(x)) + .Select(y => new JsonPrimitive(y.Value.IDString())) + .Select(x => (JsonValue)x) + .ToArray())) + ) + ); + + _fileSystem.File.WriteAllText(Path.Combine(mergeDir, "fidCache.json"), fidJson.ToString()); + + } + + private void HandleScripts( + MergeState mergeState) + { + // Console.WriteLine("HandleScript"); + } + + private void HandleAssets(MergeState mergeState) + { + var assetMerge = _assetMergeFactory(mergeState); + assetMerge.Handle(); + } +} diff --git a/MutagenMerger.Lib/MergeState.cs b/Mutagen.Bethesda.Merge.Lib/MergeState.cs similarity index 88% rename from MutagenMerger.Lib/MergeState.cs rename to Mutagen.Bethesda.Merge.Lib/MergeState.cs index afd22b4..d3804fc 100644 --- a/MutagenMerger.Lib/MergeState.cs +++ b/Mutagen.Bethesda.Merge.Lib/MergeState.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Linq; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Environments; +using Mutagen.Bethesda.Environments; using Mutagen.Bethesda.Plugins; using Mutagen.Bethesda.Plugins.Cache.Internals.Implementations; using Mutagen.Bethesda.Plugins.Records; using Noggog; -namespace MutagenMerger.Lib; +namespace Mutagen.Bethesda.Merge.Lib; public record MergeState( GameRelease Release, @@ -36,5 +33,4 @@ public FormKey GetFormKey(FormKey key) { return FormKey.Factory(key.IDString() + ":" + OutgoingMod.ModKey.FileName); } } - } diff --git a/MutagenMerger.CLI/Container/MainModule.cs b/Mutagen.Bethesda.Merge.Lib/MergerModule.cs similarity index 59% rename from MutagenMerger.CLI/Container/MainModule.cs rename to Mutagen.Bethesda.Merge.Lib/MergerModule.cs index 9642b07..c717fc5 100644 --- a/MutagenMerger.CLI/Container/MainModule.cs +++ b/Mutagen.Bethesda.Merge.Lib/MergerModule.cs @@ -1,16 +1,16 @@ using Autofac; -using MutagenMerger.Lib.DI; -using MutagenMerger.Lib.DI.GameSpecifications.Fallout4; -using MutagenMerger.Lib.DI.GameSpecifications.Oblivion; -using MutagenMerger.Lib.DI.GameSpecifications.Skyrim; +using Mutagen.Bethesda.Merge.Lib.DI; using Noggog.Autofac; +using Noggog.Autofac.Modules; -namespace MutagenMerger.CLI.Container; +namespace Mutagen.Bethesda.Merge.Lib; -public class MainModule : Autofac.Module +public class MergerModule : Autofac.Module { protected override void Load(ContainerBuilder builder) { + builder.RegisterModule(); + builder.RegisterGeneric(typeof(AssetMerge<,,,>)).AsSelf(); builder.RegisterGeneric(typeof(Merger<,,,>)).AsSelf(); builder.RegisterGeneric(typeof(CopyRecordProcessor<,>)).AsSelf(); builder.RegisterAssemblyTypes(typeof(IMerger).Assembly) diff --git a/Mutagen.Bethesda.Merge.Lib/Mutagen.Bethesda.Merge.Lib.csproj b/Mutagen.Bethesda.Merge.Lib/Mutagen.Bethesda.Merge.Lib.csproj new file mode 100644 index 0000000..cb43b99 --- /dev/null +++ b/Mutagen.Bethesda.Merge.Lib/Mutagen.Bethesda.Merge.Lib.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mutagen.Bethesda.Merge.Tests/MergerTests.cs b/Mutagen.Bethesda.Merge.Tests/MergerTests.cs new file mode 100644 index 0000000..5712e7b --- /dev/null +++ b/Mutagen.Bethesda.Merge.Tests/MergerTests.cs @@ -0,0 +1,134 @@ +using System.IO.Abstractions; +using Autofac; +using Mutagen.Bethesda.Merge.Lib; +using Mutagen.Bethesda.Merge.Lib.DI; +using Mutagen.Bethesda.Plugins; +using Mutagen.Bethesda.Skyrim; +using Mutagen.Bethesda.Testing.AutoData; +using Mutagen.Bethesda.Testing.Fakes; +using Noggog.Testing.Extensions; +using Shouldly; +using Xunit; + +namespace Mutagen.Bethesda.Merge.Tests; + +public class MergerTests +{ + public MergerTests() + { + Warmup.Init(); + } + + public class TestModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterModule(); + builder.RegisterType().AsSelf(); + } + } + + [Theory, MutagenContainerAutoData(GameRelease.SkyrimSE)] + public void TestMerging( + IFileSystem fileSystem, + ManualLoadOrderProvider loadOrderProvider, + ManualDataDirectoryProvider dataDirectory, + MutagenTestHelpers testHelpers, + ModKey modKey1, + ModKey modKey2, + ModKey modKey3, + string editorId1, + string editorId2, + string editorId3, + string editorId4, + string modifiedEditorId, + ModKey outputModKey, + Merger sut) + { + var mod1 = testHelpers.CreateDummyPlugin(modKey1, mod => + { + mod.Actions.AddNew(editorId1); + mod.Actions.AddNew(editorId2); + }); + + var mod2 = testHelpers.CreateDummyPlugin(modKey2, mod => + { + mod.Actions.AddNew(editorId3); + mod.Actions.AddNew(editorId4); + }); + + var mods = new List + { + mod1, + mod2, + }; + + using (var testMod1 = SkyrimMod.Create(SkyrimRelease.SkyrimSE) + .FromPath(Path.Combine(dataDirectory.Path, mod1)) + .WithFileSystem(fileSystem) + .Construct()) + { + var action1 = testMod1.Actions.First(); + + var mod3 = testHelpers.CreateDummyPlugin(modKey3, mod => + { + var copy = action1.DeepCopy(); + copy.EditorID = modifiedEditorId; + mod.Actions.Add(copy); + }); + + mods.Add(mod3); + } + + loadOrderProvider.SetTo(mods.ToArray()); + + sut.Merge( + modsToMerge: mods, + outputKey: outputModKey, + outputFolder: dataDirectory.Path); + + var outputFile = Path.Combine(dataDirectory.Path, outputModKey.FileName); + testHelpers.TestPlugin(outputFile, mod => + { + mod.Actions.Count.ShouldEqual(4); + + mod.Actions.ShouldContain(x => x.EditorID == modifiedEditorId); + mod.Actions.ShouldContain(x => x.EditorID == editorId2); + mod.Actions.ShouldContain(x => x.EditorID == editorId3); + mod.Actions.ShouldContain(x => x.EditorID == editorId4); + }); + } + + [Fact] + public void TestBasePluginsMerging() + { + //requires manual activation + const string folder = "base-plugins"; + if (!Directory.Exists(folder)) return; + + var mods = new List + { + "Skyrim.esm", + "Update.esm", + "Dawnguard.esm", + "HearthFires.esm", + "Dragonborn.esm" + }; + + if (!mods.All(x => File.Exists(Path.Combine(folder, x.FileName)))) + return; + + const string outputMod = "output.esp"; + var outputPath = Path.Combine(folder, outputMod); + if (File.Exists(outputPath)) + File.Delete(outputPath); + + throw new NotImplementedException(); + // using (var merger = new Merger(folder, mods, mods, outputMod, folder, GameRelease.SkyrimSE)) + // { + // merger.Merge(); + // } + + Assert.True(File.Exists(outputPath)); + } +} diff --git a/MutagenMerger.Tests/MutagenMerger.Tests.csproj b/Mutagen.Bethesda.Merge.Tests/Mutagen.Bethesda.Merge.Tests.csproj similarity index 59% rename from MutagenMerger.Tests/MutagenMerger.Tests.csproj rename to Mutagen.Bethesda.Merge.Tests/Mutagen.Bethesda.Merge.Tests.csproj index ce3b8fd..48367df 100644 --- a/MutagenMerger.Tests/MutagenMerger.Tests.csproj +++ b/Mutagen.Bethesda.Merge.Tests/Mutagen.Bethesda.Merge.Tests.csproj @@ -1,25 +1,27 @@ - net6.0;net7.0 + net9.0 false - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/Mutagen.Bethesda.Merge.Tests/MutagenTestHelpers.cs b/Mutagen.Bethesda.Merge.Tests/MutagenTestHelpers.cs new file mode 100644 index 0000000..f0ffeef --- /dev/null +++ b/Mutagen.Bethesda.Merge.Tests/MutagenTestHelpers.cs @@ -0,0 +1,48 @@ +using System.IO.Abstractions; +using Mutagen.Bethesda.Environments.DI; +using Mutagen.Bethesda.Plugins; +using Mutagen.Bethesda.Skyrim; +using Shouldly; + +namespace Mutagen.Bethesda.Merge.Tests; + +public class MutagenTestHelpers +{ + private readonly IFileSystem _fileSystem; + private readonly IDataDirectoryProvider _dataDirectoryProvider; + + public MutagenTestHelpers( + IFileSystem fileSystem, + IDataDirectoryProvider dataDirectoryProvider) + { + _fileSystem = fileSystem; + _dataDirectoryProvider = dataDirectoryProvider; + } + + public ModPath CreateDummyPlugin(ModKey modName, Action addRecords) + { + var modPath = ModPath.FromPath(Path.Combine(_dataDirectoryProvider.Path, modName.FileName)); + modPath.Path.Directory?.Create(_fileSystem); + + var mod = new SkyrimMod(modPath.ModKey, SkyrimRelease.SkyrimSE); + addRecords(mod); + mod.BeginWrite + .ToPath(modPath.Path) + .WithNoLoadOrder() + .WithFileSystem(_fileSystem) + .Write(); + + return modPath; + } + + public void TestPlugin(ModPath path, Action verify) + { + _fileSystem.File.Exists(path).ShouldBeTrue(); + + using var mod = SkyrimMod.Create(SkyrimRelease.SkyrimSE) + .FromPath(path) + .WithFileSystem(_fileSystem) + .Construct(); + verify(mod); + } +} diff --git a/MutagenMerger.sln b/Mutagen.Bethesda.Merge.sln similarity index 70% rename from MutagenMerger.sln rename to Mutagen.Bethesda.Merge.sln index f6e6e09..4c92e40 100644 --- a/MutagenMerger.sln +++ b/Mutagen.Bethesda.Merge.sln @@ -1,17 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutagenMerger.CLI", "MutagenMerger.CLI\MutagenMerger.CLI.csproj", "{7AA24461-4268-4136-8AA7-57AD53CA5047}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mutagen.Bethesda.Merge.CLI", "Mutagen.Bethesda.Merge.CLI\Mutagen.Bethesda.Merge.CLI.csproj", "{7AA24461-4268-4136-8AA7-57AD53CA5047}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutagenMerger.Lib", "MutagenMerger.Lib\MutagenMerger.Lib.csproj", "{44BA9DBC-62D1-45FD-9D5F-5C5BA5B1026D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mutagen.Bethesda.Merge.Lib", "Mutagen.Bethesda.Merge.Lib\Mutagen.Bethesda.Merge.Lib.csproj", "{44BA9DBC-62D1-45FD-9D5F-5C5BA5B1026D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A86B978E-588D-414D-8E68-6FC71A0673D7}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore + Directory.Packages.props = Directory.Packages.props + Directory.Build.props = Directory.Build.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutagenMerger.Tests", "MutagenMerger.Tests\MutagenMerger.Tests.csproj", "{B5E60238-BEAF-4E63-8EDE-BE5A3443B64A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mutagen.Bethesda.Merge.Tests", "Mutagen.Bethesda.Merge.Tests\Mutagen.Bethesda.Merge.Tests.csproj", "{B5E60238-BEAF-4E63-8EDE-BE5A3443B64A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/MutagenMerger.sln.DotSettings b/Mutagen.Bethesda.Merge.sln.DotSettings similarity index 100% rename from MutagenMerger.sln.DotSettings rename to Mutagen.Bethesda.Merge.sln.DotSettings diff --git a/MutagenMerger.CLI/MutagenMerger.CLI.csproj b/MutagenMerger.CLI/MutagenMerger.CLI.csproj deleted file mode 100644 index 9acc3ab..0000000 --- a/MutagenMerger.CLI/MutagenMerger.CLI.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net7.0 - enable - - - - - - - - - - - - - diff --git a/MutagenMerger.CLI/Options.cs b/MutagenMerger.CLI/Options.cs deleted file mode 100644 index 4b5eb97..0000000 --- a/MutagenMerger.CLI/Options.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using CommandLine; -using Mutagen.Bethesda; - -namespace MutagenMerger.CLI -{ - public class Options - { - - [Option("game", HelpText = "Game to mod (Default SkyrimSE)")] - public GameRelease Game { get; set; } = GameRelease.SkyrimSE; - - [Option("data", HelpText = "Path to the data folder", Required = true)] - public string DataFolder { get; set; } = string.Empty; - - [Option("merge", Min = 1, Max = 4096, HelpText = "Plugins to merge")] - public IEnumerable PluginsToMerge { get; set; } = Array.Empty(); - - [Option("mergefile", HelpText = "Get plugins to merge from file")] - public string PluginsMergeTxt { get; set; } = string.Empty; - - [Option("output", Required = true, HelpText = "Output merge folder")] - public string Output { get; set; } = string.Empty; - - [Option("mergename", Required = true, HelpText = "Name of Merge")] - public string MergeName { get; set; } = string.Empty; - } -} diff --git a/MutagenMerger.CLI/Program.cs b/MutagenMerger.CLI/Program.cs deleted file mode 100644 index f4eac83..0000000 --- a/MutagenMerger.CLI/Program.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Autofac; -using CommandLine; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Fallout4; -using Mutagen.Bethesda.Oblivion; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Skyrim; -using MutagenMerger.CLI.Container; -using MutagenMerger.Lib.DI; - -namespace MutagenMerger.CLI -{ - public static class Program - { - public static async Task Main(string[] args) - { - await Parser.Default.ParseArguments(args) - .WithParsedAsync(Run); - } - - private static async Task Run(Options options) - { - var modsToMerge = options.PluginsMergeTxt != string.Empty - ? (await File.ReadAllLinesAsync(options.PluginsMergeTxt)) - .Select(x => ModKey.FromNameAndExtension(x)) - .ToList() - : options.PluginsToMerge - .Select(x => ModKey.FromNameAndExtension(x)) - .ToList(); - - if (Directory.Exists(options.Output)) Directory.Delete(options.Output, true); - - var sw = new Stopwatch(); - sw.Start(); - - Type[] genericTypes; - switch (options.Game.ToCategory()) - { - case GameCategory.Oblivion: - genericTypes = new Type[] { typeof(IOblivionModGetter), typeof(IOblivionMod), typeof(IOblivionMajorRecord), typeof(IOblivionMajorRecordGetter) }; - break; - case GameCategory.Fallout4: - genericTypes = new Type[] { typeof(IFallout4ModGetter), typeof(IFallout4Mod), typeof(IFallout4MajorRecord), typeof(IFallout4MajorRecordGetter) }; - break; - case GameCategory.Skyrim: - default: - genericTypes = new Type[] { typeof(ISkyrimModGetter), typeof(ISkyrimMod), typeof(ISkyrimMajorRecord), typeof(ISkyrimMajorRecordGetter) }; - break; - } - - ContainerBuilder builder = new(); - builder.RegisterModule(); - var container = builder.Build(); - var merger = container.Resolve(typeof(Merger<,,,>).MakeGenericType(genericTypes)) as IMerger; - - merger!.Merge(options.DataFolder, modsToMerge, - ModKey.FromNameAndExtension(options.MergeName), options.Output, options.Game); - - Console.WriteLine($"Merged {modsToMerge.Count} plugins in {sw.ElapsedMilliseconds}ms"); - sw.Stop(); - } - } -} diff --git a/MutagenMerger.Lib/Assets.cs b/MutagenMerger.Lib/Assets.cs deleted file mode 100644 index 099f0b2..0000000 --- a/MutagenMerger.Lib/Assets.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Plugins.Records; -using Mutagen.Bethesda.Archives; -using Microsoft.Extensions.FileSystemGlobbing; -using Noggog; -using Microsoft.Extensions.FileSystemGlobbing.Abstractions; -using Skyrim = Mutagen.Bethesda.Skyrim; -using Fallout4 = Mutagen.Bethesda.Fallout4; -using Oblivion = Mutagen.Bethesda.Oblivion; -using Mutagen.Bethesda.Plugins.Masters; -using System.Threading.Tasks; - -namespace MutagenMerger.Lib -{ - public static class AssetMerge - where TModGetter : class, IModGetter, IMajorRecordContextEnumerable, IMajorRecordGetterEnumerable, IContextGetterMod - where TMod : class, IMod, IContextMod, TModGetter - where TMajorRecord : class, IMajorRecord, TMajorRecordGetter - where TMajorRecordGetter : class, IMajorRecordGetter - { - static MergeState _mergeState = null!; - static string _outputDir = ""; - static string _mergeName = ""; - static string temp = GetTemporaryDirectory(); - public static List Rules - { - get - { - var rules = new List() {"**/*.@(esp|esm|bsa|ba2|bsl)", "meta.ini", - "interface/translations/*.txt", "TES5Edit Backups/**/*", - "fomod/**/*", "screenshot?(s)/**/*", "scripts/source/*.psc", "source/scripts/*.psc"}; - - _mergeState.ModsToMerge.ForEach(x => - { - rules.Add($"**/{x.Name.ToLower()}.seq"); - rules.Add($"**/{x.Name.ToLower()}.ini"); - rules.Add($"**/{x.Name.ToLower()}_DISTR.ini"); - rules.Add($"**/{x.Name.ToLower()}_ANIO.ini"); - rules.Add($"**/{x.Name.ToLower()}_SWAP.ini"); - rules.Add($"**/{x.Name.ToLower()}_KID.ini"); - rules.Add($"**/{x.FileName.String.ToLower()}/**/*"); - }); - return rules; - } - } - - public static void Handle( - MergeState mergeState) - { - _mergeState = mergeState; - _outputDir = Path.GetDirectoryName(mergeState.OutputPath) ?? ""; - _mergeName = Path.GetFileName(mergeState.OutputPath); - var matcher = new Matcher(); - matcher.AddIncludePatterns(new string[] { "**/*" }); - matcher.AddExcludePatterns(Rules); - Parallel.ForEach(_mergeState.ModsToMerge, mod => { - var bsaPattern = mod.FileName.NameWithoutExtension + "*." + (_mergeState.Release == GameRelease.Fallout4 ? "b2a" : "bsa"); - string[] bsaFiles = Directory.GetFiles(_mergeState.DataPath, bsaPattern); - - // bsaFiles.ForEach(Console.WriteLine); - - foreach (string bsa in bsaFiles) - { - ExtractBSA(bsa); - } - // Console.WriteLine(); - }); - - var matches = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(temp))); - - Parallel.ForEach(matches.Files, file => { - Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(_outputDir, file.Path)) ?? ""); - Console.WriteLine(" Copying extracted asset \"" + file.Path + "\""); - File.Copy(Path.Combine(temp, file.Path), Path.Combine(_outputDir, file.Path)); - - }); - - foreach (var mod in _mergeState.ModsToMerge) - { - CopyAssets(_mergeState.DataPath, mod); - CopyAssets(temp, mod); - } - - BuildSeqFile(_mergeState.DataPath, temp, _mergeState.OutgoingMod); - - - - - Directory.Delete(temp, true); - } - - private static void BuildSeqFile(string dataPath, string temp, TMod outputMod) - { - var formIds = GetSeqQuests(outputMod); - if (formIds.Count == 0) return; - var fileName = _mergeName.Substring(0, _mergeName.Length - 4) + ".seq"; - var filePath = Path.Combine(_outputDir, "seq", fileName); - var buffer = new byte[formIds.Count * sizeof(UInt32)]; - - for (int i = 0; i < formIds.Count; i++) - { - Buffer.BlockCopy(BitConverter.GetBytes(formIds[i]), 0, buffer, i * 4, 4); - } - if (!BitConverter.IsLittleEndian) Array.Reverse(buffer); - Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? ""); - File.WriteAllBytes(filePath, buffer); - Console.WriteLine(); - Console.WriteLine(" Created SEQ file: " + fileName); - - // if (!formIds.length) return; - // let filename = fh.getFileBase(merge.filename) + '.seq', - // filePath = `${ merge.dataPath}\\seq\\${ filename}`, - // buffer = new Buffer(formIds.length * 4); - // formIds.forEach((fid, n) => buffer.writeUInt32LE(fid, n * 4)); - // fh.jetpack.write(filePath, buffer); - // progressLogger.log('Created SEQ file: ' + filePath); - } - - private static List GetSeqQuests(TMod merge) - { - var masterColl = MasterReferenceCollection.FromPath(Path.Combine(_outputDir, _mergeName), _mergeState.Release); - - IGroup quests = _mergeState.Release switch - { - GameRelease.Oblivion => merge.GetTopLevelGroup(), - GameRelease.Fallout4 => merge.GetTopLevelGroup(), - _ => merge.GetTopLevelGroup(), - }; - - List formIds = new List(); - - if (quests.Count == 0) return formIds; - - Parallel.ForEach(quests.Records, quest => { - bool startGameEnabled = _mergeState.Release switch { - GameRelease.Oblivion => ((Oblivion.Quest)quest).Data?.Flags.HasFlag(Oblivion.Quest.Flag.StartGameEnabled) ?? false, - GameRelease.Fallout4 => ((Fallout4.Quest)quest).Data?.Flags.HasFlag(Fallout4.Quest.Flag.StartGameEnabled) ?? false, - _ => ((Skyrim.Quest)quest).Flags.HasFlag(Skyrim.Quest.Flag.StartGameEnabled) - }; - if (startGameEnabled) { - var fid = masterColl.GetFormID(quest.FormKey).Raw; - formIds.Add(fid); - } - }); - - return formIds; - } - - private static void CopyAssets(string path, ModKey mod) - { - CopyActorAssets(path, "textures/actors/character/facegendata/facetint", mod); - CopyActorAssets(path, "meshes/actors/character/facegendata/facegeom", mod); - CopyActorAssets(path, "sound/voice", mod); - - CopyTranslations(path, mod); - } - - private static void ExtractBSA(string bsa) - { - Console.WriteLine(); - - var reader = Archive.CreateReader(_mergeState.Release, bsa); - var files = reader.Files.ToArray(); - Parallel.For(0,files.Count(), i => { - var file = files[i]; - var filePath = file.Path.Replace("\\", "/").ToLower(); - Directory.CreateDirectory(Path.Combine(temp, Path.GetDirectoryName(filePath) ?? "")); - Console.SetCursorPosition(0, Console.CursorTop); - Console.Write(" Extracting Archive \"" + Path.GetFileName(bsa) + "\" " + ((decimal)i / files.Count()).ToString("0.00%")); - File.WriteAllBytes(Path.Combine(temp, filePath), file.GetBytes()); - }); - Console.SetCursorPosition(0, Console.CursorTop); - Console.Write(" Extracting Archive \"" + Path.GetFileName(bsa) + "\" 100.00%"); - } - - private static void CopyTranslations(string dir, ModKey mod) - { - var path = "interface/translations/"; - var srcPath = Path.Combine(dir, path); - var dstPath = Path.Combine(_outputDir, path); - - if (!Directory.Exists(srcPath)) return; - - foreach (var file in Directory.GetFiles(srcPath, - mod.Name.ToLower() + "_*.txt", - new EnumerationOptions() { RecurseSubdirectories = true, MatchCasing = MatchCasing.CaseInsensitive })) - { - var language = Path.GetFileNameWithoutExtension(file).Replace(mod.Name.ToLower() + "_", ""); - var dst = dstPath + _mergeName.Substring(0, _mergeName.Length - 4) + "_" + language + ".txt"; - Directory.CreateDirectory(dstPath); - - var writer = File.AppendText(dst); - writer.Write(File.ReadAllText(file)); - writer.Close(); - - Console.WriteLine(" Appending " + mod.Name.ToLower() + "_" + "language to " + _mergeName.Substring(0, _mergeName.Length - 4) + "_" + language); - - - }; - } - - private static void CopyActorAssets(string dir, string _path, ModKey mod) - { - var path = _path.Replace("\\", "/"); - - var srcPath = Path.Combine(dir, path, mod.FileName.String.ToLower()); - var dstPath = Path.Combine(_outputDir, path, _mergeName); - Directory.CreateDirectory(dstPath); - - if (!Directory.Exists(srcPath)) return; - - Console.WriteLine(" Copying assets from directory \"" + Path.Combine(path, mod.FileName.String.ToLower()) + "\""); - Console.WriteLine(" Copying assets to directory \"" + Path.Combine(path, _mergeName) + "\""); - - - _mergeState.Mapping.Where(x => x.Key.ModKey == mod).ForEach(x => - { - var srcId = x.Key.ID; - var srcIdString = x.Key.IDString().ToLower(); - - foreach (var file in Directory.GetFiles(srcPath, - "*" + srcIdString + "*", - new EnumerationOptions() { RecurseSubdirectories = true, MatchCasing = MatchCasing.CaseInsensitive })) - { - if (_mergeState.Mapping.Select(x => x.Key.ModKey).Contains(mod) && _mergeState.Mapping.Select(x => x.Key.ID).Contains(srcId)) - { - var newId = "00" + _mergeState.Mapping[new FormKey(mod, srcId)].IDString().ToLower(); - var dstFile = file.Replace(srcIdString, newId).Replace(srcPath, dstPath); - Directory.CreateDirectory(Path.GetDirectoryName(dstFile) ?? ""); - Console.WriteLine(" Asset renumbered from " + srcIdString + " to " + newId); - Console.WriteLine(" Copying asset \"" + file.Replace(srcPath + "/", "") + "\" to \"" + dstFile.Replace(dstPath + "/", "") + "\""); - File.Copy(file, dstFile); - } - else - { - Directory.CreateDirectory(Path.GetDirectoryName(file.Replace(srcPath, dstPath)) ?? ""); - Console.WriteLine(" Asset not renumbered."); - Console.WriteLine(" Copying asset \"" + file.Replace(srcPath + "/", "") + "\""); - File.Copy(file, file.Replace(srcPath, dstPath)); - - } - - } - - - }); - } - - private static string GetTemporaryDirectory() - { - string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(tempDirectory); - return tempDirectory; - } - } -} diff --git a/MutagenMerger.Lib/DI/Merger.cs b/MutagenMerger.Lib/DI/Merger.cs deleted file mode 100644 index 41d86fe..0000000 --- a/MutagenMerger.Lib/DI/Merger.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Json; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Environments; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Plugins.Records; -using MutagenMerger.Lib.DI.GameSpecifications; -using Noggog; -using System.Security.Cryptography; - -namespace MutagenMerger.Lib.DI -{ - public interface IMerger - { - void Merge( - DirectoryPath dataFolderPath, - IEnumerable modsToMerge, - ModKey outputKey, - DirectoryPath outputFolder, - GameRelease game); - } - - public sealed class Merger : IMerger - where TModGetter : class, IModGetter, IContextGetterMod - where TMod : class, TModGetter, IMod, IContextMod - where TMajorRecord : class, TMajorRecordGetter, IMajorRecord - where TMajorRecordGetter : class, IMajorRecordGetter - { - private readonly IGameSpecifications _gameSpecs; - private readonly CopyRecordProcessor _copyRecordProcessor; - - public Merger( - IGameSpecifications gameSpecs, - CopyRecordProcessor copyRecordProcessor) - { - _gameSpecs = gameSpecs; - _copyRecordProcessor = copyRecordProcessor; - } - - public void Merge( - DirectoryPath dataFolderPath, - IEnumerable modsToMerge, - ModKey outputKey, - DirectoryPath outputFolder, - GameRelease game) - { - var outputMod = ModInstantiator.Activator(outputKey, game) as TMod ?? throw new Exception("Could not instantiate mod"); - var env = GameEnvironmentBuilder.Create(game).WithTargetDataFolder(dataFolderPath).WithOutputMod(outputMod).Build(); - var mods = env.LoadOrder.PriorityOrder.Resolve().ToArray(); - var mergingMods = mods.Where(x => modsToMerge.Contains(x.ModKey)).ToArray(); - - var outputFile = Path.Combine(outputFolder, outputKey.FileName); - // env.LoadOrder.ForEach(x => Console.WriteLine(x.Value.ModKey)); - - Console.WriteLine("Merging " + String.Join(", ",mergingMods.Select(x => x.ModKey.FileName.String)) + " into " + Path.GetFileName(outputFile)); - Console.WriteLine(); - - var modsToMergeSet = modsToMerge.ToHashSet(); - - var linkCache = mergingMods.ToImmutableLinkCache(); - - var state = new MergeState( - game, - mods, - modsToMergeSet, - outputMod, - OutputPath: outputFile, - DataPath: dataFolderPath, - LinkCache: linkCache, - env: env); - - _copyRecordProcessor.CopyRecords(state); - // state.Mapping.ForEach(x => Console.WriteLine(x.Key.ToString() + " " + x.Value.ToString())); - state.OutgoingMod.RemapLinks(state.Mapping); - - Directory.CreateDirectory(state.OutputPath.Directory ?? ""); - state.OutgoingMod.WriteToBinary(state.OutputPath, Utils.SafeBinaryWriteParameters(env.LoadOrder.Keys)); - - // foreach (var rec in state.OutgoingMod.EnumerateMajorRecords()) - // { - // foreach (var link in rec.EnumerateFormLinks()) - // { - // IMajorRecordGetter? majorRecord = null; - // link.TryResolveCommon(state.LinkCache, out majorRecord); - // Console.WriteLine(majorRecord?.ToString() + ":" +link.FormKey); - // } - // } - - HandleAssets(state); - - HandleScripts(state); - - MergeJson(state); - } - - private void MergeJson(MergeState state) - { - var _outputDir = Path.GetDirectoryName(state.OutputPath) ?? ""; - var _mergeName = Path.GetFileNameWithoutExtension(state.OutputPath); - var _mergePlugin = state.OutgoingMod.ModKey.FileName; - var _mergeDir = Path.Combine(_outputDir, "merge - " + _mergeName); - if (!Directory.Exists(_mergeDir)) - { - Directory.CreateDirectory(_mergeDir); - } - - JsonObject? _mergeJson = new() - { - { "name", new JsonPrimitive(_mergeName) }, - { "filename", new JsonPrimitive(_mergePlugin)}, - { "method", new JsonPrimitive("Mutagen.Bethesda.Merge")}, - { "loadOrder", new JsonArray ( - state.env.LoadOrder.PriorityOrder.Resolve().Select(x => new JsonPrimitive(x.ModKey.FileName)).ToArray() - )}, - {"dateBuilt", new JsonPrimitive(DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.zzZ"))}, - {"plugins", new JsonArray ( - state.ModsToMerge.Select(x => { MD5 md5 = MD5.Create(); return new JsonObject - { - { "filename", new JsonPrimitive(x.FileName) }, - { "hash", new JsonPrimitive(BitConverter.ToString(md5.ComputeHash(File.ReadAllBytes(Path.Combine(state.env.DataFolderPath,x.FileName)))).Replace("-", "").ToLowerInvariant()) }, - { "dataFolder", new JsonPrimitive(state.env.DataFolderPath)} - - }; }).ToArray() - )} - }; - - File.WriteAllText(Path.Combine(_mergeDir, "merge.json"), _mergeJson.ToString()); - - JsonObject? _mapJson = new( - state.ModsToMerge.Select( - x => new KeyValuePair( - x.FileName, - new JsonObject(state.Mapping.Where(y => y.Key.ModKey.Equals(x) && y.Key.ID != y.Value.ID) - .Select( - y => new KeyValuePair( - y.Key.IDString(), - new JsonPrimitive(y.Value.IDString()) - ) - ).ToArray())) - ) - ); - - File.WriteAllText(Path.Combine(_mergeDir, "map.json"), _mapJson.ToString()); - - - - JsonObject? _fidJson = new( - state.ModsToMerge.Select( - x => new KeyValuePair( - x.FileName, - new JsonArray(state.Mapping.Where(y => y.Key.ModKey.Equals(x)) - .Select( - y => new JsonPrimitive(y.Value.IDString()) - ).ToArray())) - ) - ); - - File.WriteAllText(Path.Combine(_mergeDir, "fidCache.json"), _fidJson.ToString()); - - } - - private void HandleScripts( - MergeState mergeState) - { - // Console.WriteLine("HandleScript"); - } - - private void HandleAssets(MergeState mergeState) - { - AssetMerge.Handle(mergeState); - } - } -} diff --git a/MutagenMerger.Lib/MutagenMerger.Lib.csproj b/MutagenMerger.Lib/MutagenMerger.Lib.csproj deleted file mode 100644 index 98c3d47..0000000 --- a/MutagenMerger.Lib/MutagenMerger.Lib.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net6.0;net7.0 - enable - - - - - - - - - - - - - - - - - - - - - diff --git a/MutagenMerger.Lib/Utils.cs b/MutagenMerger.Lib/Utils.cs deleted file mode 100644 index 6b71137..0000000 --- a/MutagenMerger.Lib/Utils.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Plugins.Binary.Parameters; - -namespace MutagenMerger.Lib -{ - public static class Utils - { - public static BinaryWriteParameters SafeBinaryWriteParameters (IEnumerable loadOrder) => new() - { - MasterFlag = MasterFlagOption.ChangeToMatchModKey, - ModKey = ModKeyOption.CorrectToPath, - RecordCount = RecordCountOption.Iterate, - LightMasterLimit = LightMasterLimitOption.ExceptionOnOverflow, - MastersListContent = MastersListContentOption.Iterate, - FormIDUniqueness = FormIDUniquenessOption.Iterate, - NextFormID = NextFormIDOption.Iterate, - MastersListOrdering = new MastersListOrderingByLoadOrder(loadOrder) - }; - } -} diff --git a/MutagenMerger.Tests/MergerTests.cs b/MutagenMerger.Tests/MergerTests.cs deleted file mode 100644 index 41f6d4c..0000000 --- a/MutagenMerger.Tests/MergerTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Skyrim; -using Xunit; - -namespace MutagenMerger.Tests -{ - public class MergerTests - { - public MergerTests() - { - Warmup.Init(); - } - - [Fact] - public void TestMerging() - { - const string testFolder = "merging-test-folder"; - if (Directory.Exists(testFolder)) - Directory.Delete(testFolder, true); - - var mod1 = MutagenTestHelpers.CreateDummyPlugin(testFolder, "test-file-1.esp", mod => - { - mod.Actions.AddNew("Action1"); - mod.Actions.AddNew("Action2"); - }); - - var mod2 = MutagenTestHelpers.CreateDummyPlugin(testFolder, "test-file-2.esp", mod => - { - mod.Actions.AddNew("Action3"); - mod.Actions.AddNew("Action4"); - }); - - var mods = new List - { - mod1, - mod2, - }; - - using (var testMod1 = - SkyrimMod.CreateFromBinaryOverlay(Path.Combine(testFolder, mod1), SkyrimRelease.SkyrimSE)) - { - var action1 = testMod1.Actions.First(); - - var mod3 = MutagenTestHelpers.CreateDummyPlugin(testFolder, "test-file-3.esp", mod => - { - var copy = action1.DeepCopy(); - copy.EditorID = "Action1x"; - mod.Actions.Add(copy); - }); - - mods.Add(mod3); - } - - const string outputFileName = "output.esp"; - using (var merger = new Merger(testFolder, mods, mods, outputFileName, testFolder, GameRelease.SkyrimSE)) - { - merger.Merge(); - } - - var outputFile = Path.Combine(testFolder, outputFileName); - MutagenTestHelpers.TestPlugin(outputFile, mod => - { - Assert.Equal(4, mod.Actions.Count); - - Assert.Contains(mod.Actions, x => x.EditorID == "Action1x"); - Assert.Contains(mod.Actions, x => x.EditorID == "Action2"); - Assert.Contains(mod.Actions, x => x.EditorID == "Action3"); - Assert.Contains(mod.Actions, x => x.EditorID == "Action4"); - }); - } - - [Fact] - public void TestBasePluginsMerging() - { - //requires manual activation - const string folder = "base-plugins"; - if (!Directory.Exists(folder)) return; - - var mods = new List - { - "Skyrim.esm", - "Update.esm", - "Dawnguard.esm", - "HearthFires.esm", - "Dragonborn.esm" - }; - - if (!mods.All(x => File.Exists(Path.Combine(folder, x.FileName)))) - return; - - const string outputMod = "output.esp"; - var outputPath = Path.Combine(folder, outputMod); - if (File.Exists(outputPath)) - File.Delete(outputPath); - - using (var merger = new Merger(folder, mods, mods, outputMod, folder, GameRelease.SkyrimSE)) - { - merger.Merge(); - } - - Assert.True(File.Exists(outputPath)); - } - } -} diff --git a/MutagenMerger.Tests/MutagenTestHelpers.cs b/MutagenMerger.Tests/MutagenTestHelpers.cs deleted file mode 100644 index 4919ae9..0000000 --- a/MutagenMerger.Tests/MutagenTestHelpers.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; -using Mutagen.Bethesda; -using Mutagen.Bethesda.Plugins; -using Mutagen.Bethesda.Skyrim; -using MutagenMerger.Lib; -using Xunit; - -namespace MutagenMerger.Tests -{ - public static class MutagenTestHelpers - { - public static string CreateDummyPlugin(string folder, string fileName, Action addRecords) - { - Directory.CreateDirectory(folder); - var outputPath = Path.Combine(folder, fileName); - - var mod = new SkyrimMod(ModKey.FromNameAndExtension(fileName), SkyrimRelease.SkyrimSE); - addRecords(mod); - mod.WriteToBinary(outputPath, Utils.SafeBinaryWriteParameters); - return fileName; - } - - public static void TestPlugin(string path, Action verify) - { - Assert.True(File.Exists(path)); - - using var mod = SkyrimMod.CreateFromBinaryOverlay(ModPath.FromPath(path), SkyrimRelease.SkyrimSE); - verify(mod); - } - } -} diff --git a/merge.sh b/merge.sh index 566314e..5d40b73 100755 --- a/merge.sh +++ b/merge.sh @@ -1,5 +1,5 @@ #/bin/bash -project="MutagenMerger.CLI/MutagenMerger.CLI.csproj" +project="Synthesis.Bethesda.CLI/Synthesis.Bethesda.CLI.csproj" files=$(echo "$_data"$(cat "$MO2_Instance/profiles/$profile/modlist.txt" | tac | grep -ve '^-' -ve '^#' -ve '^\*' | sed 's#^\+#'"$MO2_Instance"'/mods/#' | sed 's/.*/:&/g' | tr -d "\n\r")); cp "$MO2_Instance/profiles/$profile/plugins.txt" "$LocalAppData/$game/Plugins.txt"