From accf22a63adadc5614ed3daadecad35925287b31 Mon Sep 17 00:00:00 2001 From: Chris Knight Date: Sat, 8 Aug 2026 21:29:31 -0400 Subject: [PATCH 1/2] Fix two Vortex/DLC compatibility gaps found via comparison against IDCs/WitcherScriptMerger A different fork (github.com/IDCs/WitcherScriptMerger, the fork Vortex's real game-witcher3 extension actually downloads and drives) fixed two real gaps we'd inherited unmodified from upstream: 1. CustomLoadOrder.ProcessLine had no tolerance for "VK=" (VortexKey) lines that Vortex writes into mods.settings - a VK= line hit the catch-all "unrecognized value" branch and aborted parsing the entire file (IsValid stays false, no load order usable). Now recognized and ignored, like a comment line. 2. FileMerger's vanilla-bundle DLC-folder filter had no "bob" (Blood & Wine's internal folder codename) alternative, so conflicts inside B&W bundle content went undetected, and was case-sensitive - only ever worked by luck of Windows' case-insensitive filesystem. Now matches "bob" too, case-insensitively; more relevant for WitcherScriptMerger.Headless running on case-sensitive Linux filesystems. Extracted the DLC-folder match into FileMerger.IsVanillaDlcBundleFolder (public static, no instance state) specifically so it's directly unit testable, mirroring DiffPlexMergeEngine.BuildMerge's own reasoning for the same shape. CustomLoadOrder.ProcessLine stays private (stateful across a multi-line parse, and the class reads a real file path in its constructor) - tested via reflection instead, since constructing a CustomLoadOrder() is safe when mods.settings doesn't exist (Refresh() no-ops) and touches neither AppState.Settings nor the filesystem beyond that existence check. Verified: dotnet build clean (5 pre-existing warnings, 0 new), dotnet test 49/49 passing (18 new), dotnet format whitespace --verify-no-changes clean. AI-assisted (Claude Code) per this repo's CONTRIBUTING.md disclosure convention. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah --- .../Inventory/FileMerger.cs | 17 +++++- .../LoadOrder/CustomLoadOrder.cs | 8 +++ .../Inventory/FileMergerTests.cs | 56 ++++++++++++++++++ .../LoadOrder/CustomLoadOrderTests.cs | 58 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs create mode 100644 WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs diff --git a/WitcherScriptMerger.Core/Inventory/FileMerger.cs b/WitcherScriptMerger.Core/Inventory/FileMerger.cs index e3389cb..1baa2db 100644 --- a/WitcherScriptMerger.Core/Inventory/FileMerger.cs +++ b/WitcherScriptMerger.Core/Inventory/FileMerger.cs @@ -123,6 +123,21 @@ public class MergeReportData bool _bundleChanged; List _pendingBundleMerges = new List(); + static readonly Regex VanillaDlcBundleFolderPattern = + new Regex(@"(DLC[0-9]*|ep[0-9]|bob)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + // Matches a DLC-folder name that has its own "bundles" subfolder to search for a + // vanilla bundle: base-game expansions (DLC1, DLC2, ...), the "ep1"/"ep2" xpac + // folders, and "bob" - Blood & Wine's internal folder codename (confirmed against + // a fork's fix for this exact gap - see CLAUDE.md). IgnoreCase because a + // case-sensitive match (the prior behavior) only ever worked by luck of matching + // real-world casing on Windows' case-insensitive filesystem - WitcherScriptMerger. + // Headless runs on case-sensitive filesystems, where a differently-cased DLC folder + // would otherwise silently never match. Public and static (no instance state + // involved) specifically so it's directly unit testable, matching + // DiffPlexMergeEngine.BuildMerge's own reasoning for the same shape. + public static bool IsVanillaDlcBundleFolder(string path) => VanillaDlcBundleFolderPattern.IsMatch(path); + #endregion public FileMerger(MergeInventory inventory) @@ -714,7 +729,7 @@ bool GetUnpackedFiles(string contentRelativePath, ref MergeSource source1, ref M .Concat( Directory.Exists(Paths.DlcDirectory) ? Directory.GetDirectories(Paths.DlcDirectory) - .Where(path => new Regex("DLC[0-9]*$").IsMatch(path) || new Regex("ep[0-9]$").IsMatch(path)) + .Where(IsVanillaDlcBundleFolder) .Select(path => Path.Combine(path, Paths.BundleBase, "bundles")) : Enumerable.Empty() ) diff --git a/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs b/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs index 9428032..fd00aba 100644 --- a/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs +++ b/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs @@ -86,6 +86,14 @@ bool ProcessLine(string line, int lineNum, ref ModLoadSetting setting) if (!ProcessPriorityLine(line, lineNum, setting)) return false; } + else if (line.StartsWith("VK=")) + { + // VortexKey - written into mods.settings by Vortex's own mod-management + // integration (confirmed against a fork's fix for this exact gap, see + // CLAUDE.md); this parser has no use for it, but it's a legitimate line, + // not a malformed file, so it's recognized and ignored rather than falling + // into the catch-all warning below and aborting the whole parse. + } else if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith(";")) { ShowWarningForMalformedFile($"Unrecognized value on line {lineNum}:\n\n{line}"); diff --git a/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs b/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs new file mode 100644 index 0000000..1944016 --- /dev/null +++ b/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs @@ -0,0 +1,56 @@ +using WitcherScriptMerger.Inventory; +using Xunit; + +namespace WitcherScriptMerger.Tests.Inventory +{ + // Regression coverage for FileMerger.IsVanillaDlcBundleFolder - the DLC-folder-name + // filter GetUnpackedFiles uses to find a matching vanilla bundle. A different fork of + // this project (github.com/IDCs/WitcherScriptMerger) found and fixed two real gaps + // here that this repo had inherited unmodified from upstream: no "bob" (Blood & Wine's + // internal folder codename) alternative, and a case-sensitive match that only ever + // worked by luck of Windows' case-insensitive filesystem - see + // FileMerger.cs's own comment on VanillaDlcBundleFolderPattern and Core's CLAUDE.md. + public class FileMergerTests + { + [Theory] + [InlineData(@"C:\Witcher3\DLC\DLC1")] + [InlineData(@"C:\Witcher3\DLC\DLC13")] + [InlineData(@"C:\Witcher3\DLC\DLC")] + [InlineData(@"C:\Witcher3\DLC\ep1")] + [InlineData(@"C:\Witcher3\DLC\ep2")] + [InlineData(@"C:\Witcher3\DLC\bob")] + public void IsVanillaDlcBundleFolder_KnownVanillaDlcFolders_ReturnsTrue(string path) + { + Assert.True(FileMerger.IsVanillaDlcBundleFolder(path)); + } + + [Theory] + [InlineData(@"C:\Witcher3\DLC\dlc1")] + [InlineData(@"C:\Witcher3\DLC\Dlc1")] + [InlineData(@"C:\Witcher3\DLC\EP1")] + [InlineData(@"C:\Witcher3\DLC\Ep1")] + [InlineData(@"C:\Witcher3\DLC\BOB")] + [InlineData(@"C:\Witcher3\DLC\Bob")] + public void IsVanillaDlcBundleFolder_DifferentCasing_StillMatches(string path) + { + // The prior implementation used a case-sensitive regex, which only ever worked + // by luck of Windows' case-insensitive filesystem - would have silently never + // matched any of these on a case-sensitive filesystem (e.g. + // WitcherScriptMerger.Headless running on Linux). + Assert.True(FileMerger.IsVanillaDlcBundleFolder(path)); + } + + [Theory] + [InlineData(@"C:\Witcher3\DLC\some_other_mod")] + [InlineData(@"C:\Witcher3\DLC\bobsleigh")] + [InlineData(@"C:\Witcher3\DLC\episode1")] + [InlineData(@"")] + public void IsVanillaDlcBundleFolder_NonVanillaFolders_ReturnsFalse(string path) + { + // "bobsleigh"/"episode1" specifically confirm the pattern is anchored to the end + // of the path (via "$") rather than matching "bob"/"ep" + a digit as a bare + // substring anywhere earlier in a longer, unrelated folder name. + Assert.False(FileMerger.IsVanillaDlcBundleFolder(path)); + } + } +} diff --git a/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs b/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs new file mode 100644 index 0000000..e83eae8 --- /dev/null +++ b/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using WitcherScriptMerger.LoadOrder; +using Xunit; + +namespace WitcherScriptMerger.Tests.LoadOrder +{ + // Regression coverage for CustomLoadOrder.ProcessLine's handling of "VK=" lines. A + // different fork of this project (github.com/IDCs/WitcherScriptMerger) found that + // Vortex writes "VK=" (VortexKey) lines into mods.settings, which this parser had no + // tolerance for - falling into the catch-all "unrecognized value" branch and aborting + // the entire parse (IsValid stays false, no load order is usable) - see + // CustomLoadOrder.cs's own comment on the "VK=" branch and Core's CLAUDE.md. + // + // ProcessLine is a private instance method invoked via reflection rather than exposed + // publicly - unlike FileMerger.IsVanillaDlcBundleFolder (pure string/regex logic with + // no other coupling), ProcessLine is inherently stateful across a multi-line parse + // (accumulates a ModLoadSetting via `ref`) and CustomLoadOrder's constructor reads a + // real, fixed path under the current user's Documents folder - reflection avoids + // either widening ProcessLine's visibility or refactoring CustomLoadOrder's file-path + // coupling just for this test. Constructing a CustomLoadOrder() here is safe + // regardless of what's on the test-running machine: Refresh() no-ops (IsValid = true, + // empty Mods) when mods.settings doesn't exist, and neither Refresh() nor ProcessLine + // touches AppState.Settings (only AppState.Notifier, on the malformed-file path, + // which is safe to touch per WitcherScriptMerger.Tests/CLAUDE.md). + public class CustomLoadOrderTests + { + [Fact] + public void ProcessLine_VortexKeyLine_IsRecognizedAndIgnored() + { + var loadOrder = new CustomLoadOrder(); + var processLine = typeof(CustomLoadOrder).GetMethod("ProcessLine", BindingFlags.NonPublic | BindingFlags.Instance); + + ModLoadSetting setting = null; + object[] args = { "VK=1a2b3c4d", 1, setting }; + var result = (bool)processLine.Invoke(loadOrder, args); + + // A malformed line returns false and aborts the whole parse (see Refresh()) - + // true here confirms "VK=..." is treated as a recognized, ignorable line, not + // as "unrecognized value" like it would have been before this fix. + Assert.True(result); + } + + [Fact] + public void ProcessLine_TrulyUnrecognizedLine_StillFails() + { + // Confirms the VK= fix didn't accidentally widen ProcessLine to silently accept + // everything - a genuinely malformed line must still fail the parse. + var loadOrder = new CustomLoadOrder(); + var processLine = typeof(CustomLoadOrder).GetMethod("ProcessLine", BindingFlags.NonPublic | BindingFlags.Instance); + + ModLoadSetting setting = null; + object[] args = { "SomethingElse=1a2b3c4d", 1, setting }; + var result = (bool)processLine.Invoke(loadOrder, args); + + Assert.False(result); + } + } +} From 1c037af76c4d994ebf0c2fec93065547cc518108 Mon Sep 17 00:00:00 2001 From: Chris Knight Date: Sat, 8 Aug 2026 21:37:56 -0400 Subject: [PATCH 2/2] Address code-review findings on Vortex/DLC parity fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add a "Vortex-fork parity fixes" section to WitcherScriptMerger.Core/CLAUDE.md documenting the VK= and bob/case-insensitive-DLC gaps, and point both fix comments and the Tests/CLAUDE.md coverage list at it instead of a dangling "see CLAUDE.md" reference that didn't resolve to any actual content. - Correct FileMerger.cs's IsVanillaDlcBundleFolder comment (and the matching test comment): the prior case-sensitive regex bug was never actually caused by Windows' case-insensitive filesystem - .NET's Regex is case-sensitive on any platform - the real risk is real-world casing variance across installs. - CustomLoadOrderTests.cs now builds its CustomLoadOrder via RuntimeHelpers.GetUninitializedObject instead of the real constructor, so the test no longer depends on the state of a developer's real Documents\The Witcher 3\mods.settings file (previously safe only when that file was absent; a present-and-locked file would throw IOException unrelated to what the test covers). ProcessLine touches no instance state beyond its ref parameter, so skipping construction is safe. Reviewed-by: code-review skill (workflow-backed, medium effort) 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah --- WitcherScriptMerger.Core/CLAUDE.md | 31 +++++++++++++++++++ .../Inventory/FileMerger.cs | 16 +++++----- .../LoadOrder/CustomLoadOrder.cs | 9 +++--- WitcherScriptMerger.Tests/CLAUDE.md | 6 ++++ .../Inventory/FileMergerTests.cs | 8 ++--- .../LoadOrder/CustomLoadOrderTests.cs | 19 +++++++----- 6 files changed, 67 insertions(+), 22 deletions(-) diff --git a/WitcherScriptMerger.Core/CLAUDE.md b/WitcherScriptMerger.Core/CLAUDE.md index 8719d90..9c76ab9 100644 --- a/WitcherScriptMerger.Core/CLAUDE.md +++ b/WitcherScriptMerger.Core/CLAUDE.md @@ -126,6 +126,37 @@ ever going to remain, the interface indirection was deleted as premature abstrac own private `DiffPlexMergeEngine` field directly — there's no engine-selection step at startup in either host anymore. +## Vortex-fork parity fixes (`mods.settings` "VK=" lines, DLC-bundle-folder matching) + +Two small parity gaps versus a separate, Vortex-integrated fork of this project +(`IDCs/WitcherScriptMerger`, the fork Vortex's real `game-witcher3` extension actually +drives) were found by direct comparison and fixed here: + +- **`LoadOrder/CustomLoadOrder.ProcessLine`** now recognizes and ignores a `VK=` + (VortexKey) line instead of falling into the catch-all "unrecognized value" branch. + Vortex's own mod-management integration writes this key into `mods.settings`; without + this, `ProcessLine` returning `false` aborts `Refresh()`'s entire parse loop + (`IsValid` stays `false`, `Mods` stays empty) the moment a Vortex-managed + `mods.settings` is read. Deliberately a narrow, explicit `VK=` check rather than a + generic "tolerate any unrecognized key" change — the catch-all warning is intentional + malformed-file detection, and broadening it to accept-all would remove that + protection for a genuinely broken file. If another mod manager introduces another key + this parser doesn't know, it fails the same way `VK=` used to, by design; fix it the + same way, one recognized key at a time, rather than widening acceptance generically. +- **`Inventory/FileMerger.IsVanillaDlcBundleFolder`** (backing `GetUnpackedFiles`'s + vanilla-bundle lookup) now matches `"bob"` (Blood & Wine's internal DLC folder + codename) in addition to `DLC[0-9]*`/`ep[0-9]`, and matches case-insensitively. The + original regex had no `bob` alternative at all — Blood & Wine bundle-content + conflicts never matched against a vanilla bundle — and was case-sensitive, which + matters because real on-disk folder names vary in casing across different game/mod + installs regardless of platform. Exposed as a public static pure function (mirroring + `DiffPlexMergeEngine.BuildMerge`'s own public/static shape) specifically so it's + directly unit-testable. + +Both are regression-tested in `WitcherScriptMerger.Tests` +(`LoadOrder/CustomLoadOrderTests.cs`, `Inventory/FileMergerTests.cs`) — see +`WitcherScriptMerger.Tests/CLAUDE.md`. + ## CLI & MCP orchestration (`Cli/`, `Mcp/`) `Cli/MergeOperations.cs` is the scan-then-merge sequence shared by both hosts' `merge` diff --git a/WitcherScriptMerger.Core/Inventory/FileMerger.cs b/WitcherScriptMerger.Core/Inventory/FileMerger.cs index 1baa2db..4eab37a 100644 --- a/WitcherScriptMerger.Core/Inventory/FileMerger.cs +++ b/WitcherScriptMerger.Core/Inventory/FileMerger.cs @@ -128,13 +128,15 @@ public class MergeReportData // Matches a DLC-folder name that has its own "bundles" subfolder to search for a // vanilla bundle: base-game expansions (DLC1, DLC2, ...), the "ep1"/"ep2" xpac - // folders, and "bob" - Blood & Wine's internal folder codename (confirmed against - // a fork's fix for this exact gap - see CLAUDE.md). IgnoreCase because a - // case-sensitive match (the prior behavior) only ever worked by luck of matching - // real-world casing on Windows' case-insensitive filesystem - WitcherScriptMerger. - // Headless runs on case-sensitive filesystems, where a differently-cased DLC folder - // would otherwise silently never match. Public and static (no instance state - // involved) specifically so it's directly unit testable, matching + // folders, and "bob" - Blood & Wine's internal folder codename (see this + // project's CLAUDE.md, "Vortex-fork parity fixes" section, for the fork + // comparison this was found against). IgnoreCase because real on-disk folder + // names vary in casing across different game/mod installs - e.g. a repacked or + // differently-sourced "Bob"/"BOB" folder - and .NET's Regex is case-sensitive by + // default regardless of the underlying filesystem, so the prior case-sensitive + // match could silently miss a real vanilla DLC folder on any platform, not just + // a case-sensitive one. Public and static (no instance state involved) + // specifically so it's directly unit testable, matching // DiffPlexMergeEngine.BuildMerge's own reasoning for the same shape. public static bool IsVanillaDlcBundleFolder(string path) => VanillaDlcBundleFolderPattern.IsMatch(path); diff --git a/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs b/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs index fd00aba..90ed7ec 100644 --- a/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs +++ b/WitcherScriptMerger.Core/LoadOrder/CustomLoadOrder.cs @@ -89,10 +89,11 @@ bool ProcessLine(string line, int lineNum, ref ModLoadSetting setting) else if (line.StartsWith("VK=")) { // VortexKey - written into mods.settings by Vortex's own mod-management - // integration (confirmed against a fork's fix for this exact gap, see - // CLAUDE.md); this parser has no use for it, but it's a legitimate line, - // not a malformed file, so it's recognized and ignored rather than falling - // into the catch-all warning below and aborting the whole parse. + // integration (see this project's CLAUDE.md, "Vortex-fork parity fixes" + // section, for the fork comparison this was found against); this parser + // has no use for it, but it's a legitimate line, not a malformed file, so + // it's recognized and ignored rather than falling into the catch-all + // warning below and aborting the whole parse. } else if (!string.IsNullOrWhiteSpace(line) && !line.StartsWith(";")) { diff --git a/WitcherScriptMerger.Tests/CLAUDE.md b/WitcherScriptMerger.Tests/CLAUDE.md index d8ca573..7085bfa 100644 --- a/WitcherScriptMerger.Tests/CLAUDE.md +++ b/WitcherScriptMerger.Tests/CLAUDE.md @@ -20,6 +20,12 @@ does. `MergeHeadless_EncodingMismatch_...` fixture reproducing the `baseEffect.ws`-style false conflict that motivated it. - `Tools/HasherTests.cs` — `Hasher`'s xxHash32 output, including synthetic edge cases. +- `Inventory/FileMergerTests.cs` — `FileMerger.IsVanillaDlcBundleFolder`: known vanilla + DLC-folder names, case-insensitivity, and non-matches (including anchoring) — see + Core's `CLAUDE.md`'s "Vortex-fork parity fixes" section. +- `LoadOrder/CustomLoadOrderTests.cs` — `CustomLoadOrder.ProcessLine`'s tolerance for + `mods.settings` "VK=" (VortexKey) lines, via reflection — see Core's `CLAUDE.md`'s + "Vortex-fork parity fixes" section. - `Tools/KDiff3CrossCheckTests.cs` — an auto-solvable-only A/B check of `DiffPlexMergeEngine` against a real `KDiff3.exe` binary, when a developer happens to have one locally (WSM no longer bundles or requires KDiff3 itself — see diff --git a/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs b/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs index 1944016..8c3e08f 100644 --- a/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs +++ b/WitcherScriptMerger.Tests/Inventory/FileMergerTests.cs @@ -33,10 +33,10 @@ public void IsVanillaDlcBundleFolder_KnownVanillaDlcFolders_ReturnsTrue(string p [InlineData(@"C:\Witcher3\DLC\Bob")] public void IsVanillaDlcBundleFolder_DifferentCasing_StillMatches(string path) { - // The prior implementation used a case-sensitive regex, which only ever worked - // by luck of Windows' case-insensitive filesystem - would have silently never - // matched any of these on a case-sensitive filesystem (e.g. - // WitcherScriptMerger.Headless running on Linux). + // The prior implementation used a case-sensitive regex - since .NET's Regex is + // case-sensitive by default regardless of platform, it could silently miss a real + // vanilla DLC folder whose on-disk casing simply differs (e.g. a + // differently-sourced or repacked install), on any platform. Assert.True(FileMerger.IsVanillaDlcBundleFolder(path)); } diff --git a/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs b/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs index e83eae8..9862c6c 100644 --- a/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs +++ b/WitcherScriptMerger.Tests/LoadOrder/CustomLoadOrderTests.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using WitcherScriptMerger.LoadOrder; using Xunit; @@ -17,17 +18,21 @@ namespace WitcherScriptMerger.Tests.LoadOrder // (accumulates a ModLoadSetting via `ref`) and CustomLoadOrder's constructor reads a // real, fixed path under the current user's Documents folder - reflection avoids // either widening ProcessLine's visibility or refactoring CustomLoadOrder's file-path - // coupling just for this test. Constructing a CustomLoadOrder() here is safe - // regardless of what's on the test-running machine: Refresh() no-ops (IsValid = true, - // empty Mods) when mods.settings doesn't exist, and neither Refresh() nor ProcessLine - // touches AppState.Settings (only AppState.Notifier, on the malformed-file path, - // which is safe to touch per WitcherScriptMerger.Tests/CLAUDE.md). + // coupling just for this test. The instance itself is created via + // RuntimeHelpers.GetUninitializedObject rather than `new CustomLoadOrder()`, skipping + // the constructor (and its Refresh() call) entirely - ProcessLine touches no instance + // state beyond the `ref` setting parameter, so it needs no initialized instance, and + // skipping construction avoids depending on the test-running machine's real + // mods.settings file, which - unlike the "file doesn't exist" case Refresh() no-ops + // on safely - could be present and locked by a running game/Vortex process on a + // developer machine with a live install, throwing IOException for a reason unrelated + // to what this test actually covers. public class CustomLoadOrderTests { [Fact] public void ProcessLine_VortexKeyLine_IsRecognizedAndIgnored() { - var loadOrder = new CustomLoadOrder(); + var loadOrder = (CustomLoadOrder)RuntimeHelpers.GetUninitializedObject(typeof(CustomLoadOrder)); var processLine = typeof(CustomLoadOrder).GetMethod("ProcessLine", BindingFlags.NonPublic | BindingFlags.Instance); ModLoadSetting setting = null; @@ -45,7 +50,7 @@ public void ProcessLine_TrulyUnrecognizedLine_StillFails() { // Confirms the VK= fix didn't accidentally widen ProcessLine to silently accept // everything - a genuinely malformed line must still fail the parse. - var loadOrder = new CustomLoadOrder(); + var loadOrder = (CustomLoadOrder)RuntimeHelpers.GetUninitializedObject(typeof(CustomLoadOrder)); var processLine = typeof(CustomLoadOrder).GetMethod("ProcessLine", BindingFlags.NonPublic | BindingFlags.Instance); ModLoadSetting setting = null;