From 43d0e2592f1595b819b3cca93af3be60c03ed8dd Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 05:03:56 -0700 Subject: [PATCH 01/33] Fix: Don't use invalid array indices when parsing openmw data directories --- TES3Merge/Util/Installation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 1657925..2fcc23e 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -419,6 +419,9 @@ private void LoadConfiguration() foreach (var line in File.ReadLines(configPath)) { var tokens = line.Split('=', 2); + + if (tokens.Length < 2) continue; + var key = tokens[0].Trim(); var value = tokens[1].Trim(new char[] { ' ', '"' }); From d55b72e955dba43084b75e48b50189a7163af5d1 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 05:04:23 -0700 Subject: [PATCH 02/33] FIX: Ignore commented lines during openmw config parsing --- TES3Merge/Util/Installation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 2fcc23e..d38521e 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -418,6 +418,8 @@ private void LoadConfiguration() foreach (var line in File.ReadLines(configPath)) { + if (line.Trim().StartsWith("#")) continue; + var tokens = line.Split('=', 2); if (tokens.Length < 2) continue; From 4b24e2525c762bd7b730e8267e909f78bcda5d49 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 05:05:46 -0700 Subject: [PATCH 03/33] FIX: Only replace forward slashes with backslashes on platforms where it's actually necessary --- TES3Merge/Util/Installation.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index d38521e..4e449e0 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -430,7 +430,9 @@ private void LoadConfiguration() switch (key) { case "data": - DataDirectories.Add(value.Replace('/', '\\')); + var shouldUseBackslash = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var configDir = shouldUseBackslash ? value.Replace('/', '\\') : value; + DataDirectories.Add(configDir); break; case "content": GameFiles.Add(value); From 596179400b3a9d3cd97018222c8172d134d5ee4c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 05:06:21 -0700 Subject: [PATCH 04/33] FIX: Actually use the configPath parameter passed to OpenMWInstallation constructor --- TES3Merge/Util/Installation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 4e449e0..722a133 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -384,7 +384,7 @@ public class OpenMWInstallation : Installation public OpenMWInstallation(string path) : base(path) { - LoadConfiguration(); + LoadConfiguration(path); } private static string GetConfigurationLocation() @@ -408,9 +408,9 @@ private static string GetConfigurationLocation() throw new Exception("Could not determine configuration path."); } - private void LoadConfiguration() + private void LoadConfiguration(string configPath) { - var configPath = GetConfigurationLocation(); + configPath = Path.Combine(configPath, "openmw.cfg"); if (!File.Exists(configPath)) { throw new Exception("Configuration file does not exist."); From 9722d47b16b386b2804b8e52b9651d5883effc56 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 05:06:48 -0700 Subject: [PATCH 05/33] FIX: Don't trim the first character off of entries which don't start with a path separator --- TES3Merge/Util/Installation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 722a133..9ac0570 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -482,7 +482,7 @@ private void MapNormalFiles(string dataFiles) .GetFiles(dataFiles, "*", SearchOption.AllDirectories) .Where(x => !x.EndsWith(".mohidden")) //.Where(x => !x.Contains(Path.DirectorySeparatorChar + ".git" + Path.DirectorySeparatorChar)) - .Select(x => x[(dataFiles.Length + 1)..]); + .Select(x => Path.GetRelativePath(dataFiles, x)); foreach (var file in physicalFiles) { From f629bfbf6079759fbfb152ac8fa9d7d4f6126f39 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:02:17 -0700 Subject: [PATCH 06/33] FIX: Properly handle quoted setting values in openmw.cfg --- TES3Merge/Util/Installation.cs | 39 +++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 9ac0570..4e2a025 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -260,7 +260,8 @@ private void BuildArchiveList() for (var i = 0; true; ++i) { var archive = configArchives["Archive " + i]; - if (string.IsNullOrEmpty(archive)) { + if (string.IsNullOrEmpty(archive)) + { break; } Archives.Add(archive); @@ -408,6 +409,28 @@ private static string GetConfigurationLocation() throw new Exception("Could not determine configuration path."); } + private static string UnescapeAmpersands(string input) + { + var result = new System.Text.StringBuilder(input.Length); + for (int i = 0; i < input.Length; i++) + { + if (input[i] == '&') + { + // If next char is also &, keep one and skip the next + if (i + 1 < input.Length && input[i + 1] == '&') + { + result.Append('&'); + i++; + } + } + else + { + result.Append(input[i]); + } + } + return result.ToString(); + } + private void LoadConfiguration(string configPath) { configPath = Path.Combine(configPath, "openmw.cfg"); @@ -418,18 +441,28 @@ private void LoadConfiguration(string configPath) foreach (var line in File.ReadLines(configPath)) { - if (line.Trim().StartsWith("#")) continue; + if (line.IsNullOrEmpty || line.Trim().StartsWith("#")) continue; var tokens = line.Split('=', 2); if (tokens.Length < 2) continue; var key = tokens[0].Trim(); - var value = tokens[1].Trim(new char[] { ' ', '"' }); + var value = tokens[1].Trim(); switch (key) { case "data": + if (value.StartsWith('"')) + { + int lastQuote = value.LastIndexOf('"'); + if (lastQuote > 0) + { + value = value.Substring(0, lastQuote + 1); + } + value = UnescapeAmpersands(value.Trim('"')); + } + var shouldUseBackslash = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var configDir = shouldUseBackslash ? value.Replace('/', '\\') : value; DataDirectories.Add(configDir); From e0a0163891007ae9864b192d4b2aea735705e151 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:09:38 -0700 Subject: [PATCH 07/33] CLEANUP: Slightly more polite separator replacement --- TES3Merge/Util/Installation.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 4e2a025..0e23079 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -463,9 +463,10 @@ private void LoadConfiguration(string configPath) value = UnescapeAmpersands(value.Trim('"')); } - var shouldUseBackslash = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - var configDir = shouldUseBackslash ? value.Replace('/', '\\') : value; - DataDirectories.Add(configDir); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + value = value.Replace('/', '\\'); + + DataDirectories.Add(value); break; case "content": GameFiles.Add(value); From 944691d4308c965484cf7099ef45c42d089c0233 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:21:28 -0700 Subject: [PATCH 08/33] FEAT: Absolutize paths in openmw.cfg --- TES3Merge/Util/Installation.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 0e23079..8b040b4 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -431,9 +431,9 @@ private static string UnescapeAmpersands(string input) return result.ToString(); } - private void LoadConfiguration(string configPath) + private void LoadConfiguration(string configDir) { - configPath = Path.Combine(configPath, "openmw.cfg"); + var configPath = Path.Combine(configDir, "openmw.cfg"); if (!File.Exists(configPath)) { throw new Exception("Configuration file does not exist."); @@ -466,6 +466,9 @@ private void LoadConfiguration(string configPath) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) value = value.Replace('/', '\\'); + if (!Path.IsPathRooted(value)) + value = Path.GetFullPath(Path.Combine(configDir, value)); + DataDirectories.Add(value); break; case "content": From c728a442091b3e80bce6f128195326edbad1c6ae Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:45:01 -0700 Subject: [PATCH 09/33] FEAT: Handle the data-local directory during config parsing --- TES3Merge/Util/Installation.cs | 73 ++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 8b040b4..6089d5a 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -382,10 +382,13 @@ public override string GetDefaultOutputDirectory() public class OpenMWInstallation : Installation { private List DataDirectories = new(); + private string DataLocalDirectory; public OpenMWInstallation(string path) : base(path) { LoadConfiguration(path); + if (!string.IsNullOrEmpty(DataLocalDirectory)) + DataDirectories.Add(ParseDataDirectory(DataLocalDirectory)); } private static string GetConfigurationLocation() @@ -409,6 +412,31 @@ private static string GetConfigurationLocation() throw new Exception("Could not determine configuration path."); } + private static string GetDataLocalDirectory() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + return Path.Combine(myDocs, "My Games", "OpenMW", "data"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + string dataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + + if (string.IsNullOrEmpty(dataHome)) + dataHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); + + return Path.Combine(dataHome, "openmw", "data"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(home, "Library", "Application Support", "openmw", "data"); + } + + throw new Exception("Could not determine user data directory."); + } + private static string UnescapeAmpersands(string input) { var result = new System.Text.StringBuilder(input.Length); @@ -431,6 +459,27 @@ private static string UnescapeAmpersands(string input) return result.ToString(); } + private static string ParseDataDirectory(string value) + { + if (value.StartsWith('"')) + { + int lastQuote = value.LastIndexOf('"'); + if (lastQuote > 0) + { + value = value.Substring(0, lastQuote + 1); + } + value = UnescapeAmpersands(value.Trim('"')); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + value = value.Replace('/', '\\'); + + if (!Path.IsPathRooted(value)) + value = Path.GetFullPath(Path.Combine(configDir, value)); + + return value; + } + private void LoadConfiguration(string configDir) { var configPath = Path.Combine(configDir, "openmw.cfg"); @@ -453,23 +502,7 @@ private void LoadConfiguration(string configDir) switch (key) { case "data": - if (value.StartsWith('"')) - { - int lastQuote = value.LastIndexOf('"'); - if (lastQuote > 0) - { - value = value.Substring(0, lastQuote + 1); - } - value = UnescapeAmpersands(value.Trim('"')); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - value = value.Replace('/', '\\'); - - if (!Path.IsPathRooted(value)) - value = Path.GetFullPath(Path.Combine(configDir, value)); - - DataDirectories.Add(value); + DataDirectories.Add(ParseDataDirectory(value)); break; case "content": GameFiles.Add(value); @@ -477,6 +510,12 @@ private void LoadConfiguration(string configDir) case "fallback-archive": Archives.Add(value); break; + case "data-local": + if (value == "?data-local?") + value = GetDataLocalDirectory(); + + DataLocalDirectory = value; + break; } } } From 54d27fd6f9d28ad6cbf95796abcf6c59456d8c5b Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:47:09 -0700 Subject: [PATCH 10/33] FIX: In the case of parsing a local or global openmw.cfg, create a data-local directory which doesn't already exist --- TES3Merge/Util/Installation.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 6089d5a..069c8df 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -512,8 +512,13 @@ private void LoadConfiguration(string configDir) break; case "data-local": if (value == "?data-local?") + { value = GetDataLocalDirectory(); + if (!Directory.Exists(value)) + Directory.CreateDirectory(value); + } + DataLocalDirectory = value; break; } From c8c23e7b5a0fbbe187ea18292c45d09fb17b7391 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 12:51:15 -0700 Subject: [PATCH 11/33] FEAT: Refactor GetConfigurationLocation to return the default config directory instead, and make LoadConfiguration recurse onto itself when config keys are encountered to handle layered openmw.cfg configurations --- TES3Merge/Util/Installation.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 069c8df..b85c00c 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -391,22 +391,22 @@ public OpenMWInstallation(string path) : base(path) DataDirectories.Add(ParseDataDirectory(DataLocalDirectory)); } - private static string GetConfigurationLocation() + private static string GetConfigurationDirectory() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); - return Path.Combine(myDocs, "My Games", "OpenMW", "openmw.cfg"); + return Path.Combine(myDocs, "My Games", "OpenMW"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - return Path.Combine(home, ".config", "openmw", "openmw.cfg"); + return Path.Combine(home, ".config", "openmw"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - return Path.Combine(home, "Library", "Preferences", "openmw", "openmw.cfg"); + return Path.Combine(home, "Library", "Preferences", "openmw"); } throw new Exception("Could not determine configuration path."); @@ -521,6 +521,12 @@ private void LoadConfiguration(string configDir) DataLocalDirectory = value; break; + case "config": + if (value == "?userconfig?") + value = GetConfigurationDirectory(); + + LoadConfiguration(value); + break; } } } From 51f96a012014e575a84076870fcb936c059a0782 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 13:23:48 -0700 Subject: [PATCH 12/33] CLEANUP: Fix compile errors, rename some variables, and correctly handle configruation-defined configuration directories --- TES3Merge/Util/Installation.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index b85c00c..ce3a3ef 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -382,13 +382,13 @@ public override string GetDefaultOutputDirectory() public class OpenMWInstallation : Installation { private List DataDirectories = new(); - private string DataLocalDirectory; + private string? DataLocalDirectory; public OpenMWInstallation(string path) : base(path) { LoadConfiguration(path); if (!string.IsNullOrEmpty(DataLocalDirectory)) - DataDirectories.Add(ParseDataDirectory(DataLocalDirectory)); + DataDirectories.Add(ParseDataDirectory(path, DataLocalDirectory)); } private static string GetConfigurationDirectory() @@ -421,7 +421,7 @@ private static string GetDataLocalDirectory() } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - string dataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + var dataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); if (string.IsNullOrEmpty(dataHome)) dataHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); @@ -459,25 +459,25 @@ private static string UnescapeAmpersands(string input) return result.ToString(); } - private static string ParseDataDirectory(string value) + private static string ParseDataDirectory(string configDir, string dataDir) { - if (value.StartsWith('"')) + if (dataDir.StartsWith('"')) { - int lastQuote = value.LastIndexOf('"'); + int lastQuote = dataDir.LastIndexOf('"'); if (lastQuote > 0) { - value = value.Substring(0, lastQuote + 1); + dataDir = dataDir.Substring(0, lastQuote + 1); } - value = UnescapeAmpersands(value.Trim('"')); + dataDir = UnescapeAmpersands(dataDir.Trim('"')); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - value = value.Replace('/', '\\'); + dataDir = dataDir.Replace('/', '\\'); - if (!Path.IsPathRooted(value)) - value = Path.GetFullPath(Path.Combine(configDir, value)); + if (!Path.IsPathRooted(dataDir)) + dataDir = Path.GetFullPath(Path.Combine(configDir, dataDir)); - return value; + return dataDir; } private void LoadConfiguration(string configDir) @@ -490,7 +490,7 @@ private void LoadConfiguration(string configDir) foreach (var line in File.ReadLines(configPath)) { - if (line.IsNullOrEmpty || line.Trim().StartsWith("#")) continue; + if (string.IsNullOrEmpty(line) || line.Trim().StartsWith("#")) continue; var tokens = line.Split('=', 2); @@ -502,7 +502,7 @@ private void LoadConfiguration(string configDir) switch (key) { case "data": - DataDirectories.Add(ParseDataDirectory(value)); + DataDirectories.Add(ParseDataDirectory(configDir, value)); break; case "content": GameFiles.Add(value); @@ -523,9 +523,9 @@ private void LoadConfiguration(string configDir) break; case "config": if (value == "?userconfig?") - value = GetConfigurationDirectory(); - - LoadConfiguration(value); + LoadConfiguration(GetConfigurationDirectory()); + else + LoadConfiguration(ParseDataDirectory(configDir, value)); break; } } From 71b17635037df04ad98d7b9bcade1a609257a913 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 20:20:41 -0700 Subject: [PATCH 13/33] FIX: Interpret the last double-quote without a trailing ampersand as the end of a quoted path instead of the literal last double-quote --- TES3Merge/Util/Installation.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index ce3a3ef..2ec8240 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -463,7 +463,25 @@ private static string ParseDataDirectory(string configDir, string dataDir) { if (dataDir.StartsWith('"')) { - int lastQuote = dataDir.LastIndexOf('"'); + int lastQuote = -1; + for (int i = dataDir.Length - 1; i > 0; i--) + { + if (dataDir[i] == '"') + { + int ampCount = 0; + int j = i - 1; + while (j >= 0 && dataDir[j] == '&') + { + ampCount++; + j--; + } + if (ampCount % 2 == 0) + { + lastQuote = i; + break; + } + } + } if (lastQuote > 0) { dataDir = dataDir.Substring(0, lastQuote + 1); From 1b32b3ced3eb5dc1be07256f7767e07185f35a4a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:09:06 -0700 Subject: [PATCH 14/33] CLEANUP: data-local is not really its own thing by default but instead a child of the userdata directory --- TES3Merge/Util/Installation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 2ec8240..2a1cc17 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -417,7 +417,7 @@ private static string GetDataLocalDirectory() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); - return Path.Combine(myDocs, "My Games", "OpenMW", "data"); + return Path.Combine(myDocs, "My Games", "OpenMW"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { @@ -426,12 +426,12 @@ private static string GetDataLocalDirectory() if (string.IsNullOrEmpty(dataHome)) dataHome = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share"); - return Path.Combine(dataHome, "openmw", "data"); + return Path.Combine(dataHome, "openmw"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - return Path.Combine(home, "Library", "Application Support", "openmw", "data"); + return Path.Combine(home, "Library", "Application Support", "openmw"); } throw new Exception("Could not determine user data directory."); From 45cdae095cd6f77f2844a1855a997aa7a3ce8eed Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:10:42 -0700 Subject: [PATCH 15/33] FIX: Handle data-local parsing the same way as all other data directories --- TES3Merge/Util/Installation.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 2a1cc17..a93b684 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -529,13 +529,10 @@ private void LoadConfiguration(string configDir) Archives.Add(value); break; case "data-local": - if (value == "?data-local?") - { - value = GetDataLocalDirectory(); + value = ParseDataDirectory(configDir, value); - if (!Directory.Exists(value)) - Directory.CreateDirectory(value); - } + if (!Directory.Exists(value)) + Directory.CreateDirectory(value); DataLocalDirectory = value; break; From 290a46e2b93731014617b0af6c031a32f90e4e68 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:11:29 -0700 Subject: [PATCH 16/33] CLEANUP: Don't special-case ?userconfig? and instead handle `config` keys using normal data directory parsing --- TES3Merge/Util/Installation.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index a93b684..f58b704 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -537,10 +537,7 @@ private void LoadConfiguration(string configDir) DataLocalDirectory = value; break; case "config": - if (value == "?userconfig?") - LoadConfiguration(GetConfigurationDirectory()); - else - LoadConfiguration(ParseDataDirectory(configDir, value)); + LoadConfiguration(ParseDataDirectory(configDir, value)); break; } } From 854c2884579cc7a1a018ad03a6354f75e7b7bc17 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:18:55 -0700 Subject: [PATCH 17/33] FEAT: Correctly handle token usage in data directories (mostly) --- TES3Merge/Util/Installation.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index f58b704..49b3ed6 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -391,7 +391,7 @@ public OpenMWInstallation(string path) : base(path) DataDirectories.Add(ParseDataDirectory(path, DataLocalDirectory)); } - private static string GetConfigurationDirectory() + private static string GetDefaultConfigurationDirectory() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -412,7 +412,7 @@ private static string GetConfigurationDirectory() throw new Exception("Could not determine configuration path."); } - private static string GetDataLocalDirectory() + private static string GetDefaultUserDataDirectory() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -461,6 +461,11 @@ private static string UnescapeAmpersands(string input) private static string ParseDataDirectory(string configDir, string dataDir) { + if (dataDir.StartsWith("?userdata?")) + dataDir = dataDir.Replace("?userdata?", GetDefaultUserDataDirectory() + Path.PathSeparator); + else if (dataDir.StartsWith("?userconfig?")) + dataDir = dataDir.Replace("?userconfig?", GetDefaultConfigurationDirectory() + Path.PathSeparator); + if (dataDir.StartsWith('"')) { int lastQuote = -1; From 7310d6f7ab4bdcaefbcda68585d1002853e46f3c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:30:38 -0700 Subject: [PATCH 18/33] FIX: Token replacement should actually occur after quote handling --- TES3Merge/Util/Installation.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 49b3ed6..7a4539b 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -461,11 +461,6 @@ private static string UnescapeAmpersands(string input) private static string ParseDataDirectory(string configDir, string dataDir) { - if (dataDir.StartsWith("?userdata?")) - dataDir = dataDir.Replace("?userdata?", GetDefaultUserDataDirectory() + Path.PathSeparator); - else if (dataDir.StartsWith("?userconfig?")) - dataDir = dataDir.Replace("?userconfig?", GetDefaultConfigurationDirectory() + Path.PathSeparator); - if (dataDir.StartsWith('"')) { int lastQuote = -1; @@ -494,6 +489,11 @@ private static string ParseDataDirectory(string configDir, string dataDir) dataDir = UnescapeAmpersands(dataDir.Trim('"')); } + if (dataDir.StartsWith("?userdata?")) + dataDir = dataDir.Replace("?userdata?", GetDefaultUserDataDirectory() + Path.PathSeparator); + else if (dataDir.StartsWith("?userconfig?")) + dataDir = dataDir.Replace("?userconfig?", GetDefaultConfigurationDirectory() + Path.PathSeparator); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) dataDir = dataDir.Replace('/', '\\'); From ab72da2f8247199ba67140105b25a9055c188851 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 21:35:30 -0700 Subject: [PATCH 19/33] FEAT: Also trim duplicate and trailing path separators from anything interpreted to be a data directory --- TES3Merge/Util/Installation.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 7a4539b..831c2bf 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -2,6 +2,7 @@ using IniParser.Model; using Microsoft.Win32; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using TES3Merge.BSA; namespace TES3Merge.Util; @@ -383,6 +384,8 @@ public class OpenMWInstallation : Installation { private List DataDirectories = new(); private string? DataLocalDirectory; + private static readonly string DuplicateSeparatorPattern = + $"[{Regex.Escape($"{Path.DirectorySeparatorChar}{Path.AltDirectorySeparatorChar}")}]+"; public OpenMWInstallation(string path) : base(path) { @@ -500,6 +503,9 @@ private static string ParseDataDirectory(string configDir, string dataDir) if (!Path.IsPathRooted(dataDir)) dataDir = Path.GetFullPath(Path.Combine(configDir, dataDir)); + dataDir = dataDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + dataDir = Regex.Replace(dataDir, DuplicateSeparatorPattern, Path.DirectorySeparatorChar.ToString()); + return dataDir; } From 2487524cb41c298d2af1c05579c1b64ce581888e Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 22:01:37 -0700 Subject: [PATCH 20/33] CLEANUP: Only parse the data-local directory and verify its existence once --- TES3Merge/Util/Installation.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 831c2bf..4144031 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -391,7 +391,13 @@ public OpenMWInstallation(string path) : base(path) { LoadConfiguration(path); if (!string.IsNullOrEmpty(DataLocalDirectory)) - DataDirectories.Add(ParseDataDirectory(path, DataLocalDirectory)); + { + var realDataDirectory = ParseDataDirectory(path, DataLocalDirectory); + DataDirectories.Add(realDataDirectory); + + if (!Directory.Exists(realDataDirectory)) + Directory.CreateDirectory(realDataDirectory); + } } private static string GetDefaultConfigurationDirectory() @@ -540,11 +546,6 @@ private void LoadConfiguration(string configDir) Archives.Add(value); break; case "data-local": - value = ParseDataDirectory(configDir, value); - - if (!Directory.Exists(value)) - Directory.CreateDirectory(value); - DataLocalDirectory = value; break; case "config": From 9c1bf1350e7811f6e87268c8d77119155b60f9cd Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 22:02:28 -0700 Subject: [PATCH 21/33] FEAT: Handle the resources directory since we did the other things anyway --- TES3Merge/Util/Installation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 4144031..224505c 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -384,12 +384,14 @@ public class OpenMWInstallation : Installation { private List DataDirectories = new(); private string? DataLocalDirectory; + private string? ResourcesDirectory; private static readonly string DuplicateSeparatorPattern = $"[{Regex.Escape($"{Path.DirectorySeparatorChar}{Path.AltDirectorySeparatorChar}")}]+"; public OpenMWInstallation(string path) : base(path) { LoadConfiguration(path); + if (!string.IsNullOrEmpty(DataLocalDirectory)) { var realDataDirectory = ParseDataDirectory(path, DataLocalDirectory); @@ -398,6 +400,9 @@ public OpenMWInstallation(string path) : base(path) if (!Directory.Exists(realDataDirectory)) Directory.CreateDirectory(realDataDirectory); } + + if (!string.IsNullOrEmpty(ResourcesDirectory)) + DataDirectories.Insert(0, Path.Combine(ParseDataDirectory(path, ResourcesDirectory), "vfs")); } private static string GetDefaultConfigurationDirectory() @@ -551,6 +556,9 @@ private void LoadConfiguration(string configDir) case "config": LoadConfiguration(ParseDataDirectory(configDir, value)); break; + case "resources": + ResourcesDirectory = ParseDataDirectory(configDir, value); + break; } } } From c86b5989cc524453d9a097ac058c879e63b02115 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 04:51:47 -0700 Subject: [PATCH 22/33] CLEANUP: Prevent log spam by skipping omwscripts files --- TES3Merge/Util/Installation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 224505c..6c813e0 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -545,6 +545,8 @@ private void LoadConfiguration(string configDir) DataDirectories.Add(ParseDataDirectory(configDir, value)); break; case "content": + if (value.ToLower().EndsWith(".omwscripts")) continue; + GameFiles.Add(value); break; case "fallback-archive": From 9627b1a2b182886a9698c5367df82c4bbe5da392 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 04:52:10 -0700 Subject: [PATCH 23/33] FIX: Don't attempt to load directories which are config keys but don't have an openmw.cfg in them --- TES3Merge/Util/Installation.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 6c813e0..966c62c 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -556,7 +556,14 @@ private void LoadConfiguration(string configDir) DataLocalDirectory = value; break; case "config": - LoadConfiguration(ParseDataDirectory(configDir, value)); + try + { + LoadConfiguration(ParseDataDirectory(configDir, value)); + } + catch + { + Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + "does not contain an openmw.cfg, skipping"); + } break; case "resources": ResourcesDirectory = ParseDataDirectory(configDir, value); From dac1b9092e009c215630de55a8f0f2adf6c3b4ca Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 05:08:54 -0700 Subject: [PATCH 24/33] CLEANUP: Throw on duplicate content files --- TES3Merge/Util/Installation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 966c62c..50c0d03 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -546,6 +546,8 @@ private void LoadConfiguration(string configDir) break; case "content": if (value.ToLower().EndsWith(".omwscripts")) continue; + else if (GameFiles.Contains(value)) + throw new Exception(value + " was listed as a content file by two configurations! The second one was: " + configDir); GameFiles.Add(value); break; From 2cce684867183e25a98b803ed1649dc77c9e72e1 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 05:13:47 -0700 Subject: [PATCH 25/33] CLEANUP: Fix spacing in log output when a sub-configuration isn't loaded --- TES3Merge/Util/Installation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 50c0d03..63f27c7 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -564,7 +564,7 @@ private void LoadConfiguration(string configDir) } catch { - Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + "does not contain an openmw.cfg, skipping"); + Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + " does not contain an openmw.cfg, skipping"); } break; case "resources": From c60155b831af1a50d6a45802d5f463e749054c6d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 08:44:52 -0700 Subject: [PATCH 26/33] CLEANUP: I guess I did badly overcomplicate that, didn't I? --- TES3Merge/Util/Installation.cs | 52 ++++++---------------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 63f27c7..9be9eb2 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -451,56 +451,20 @@ private static string GetDefaultUserDataDirectory() throw new Exception("Could not determine user data directory."); } - private static string UnescapeAmpersands(string input) - { - var result = new System.Text.StringBuilder(input.Length); - for (int i = 0; i < input.Length; i++) - { - if (input[i] == '&') - { - // If next char is also &, keep one and skip the next - if (i + 1 < input.Length && input[i + 1] == '&') - { - result.Append('&'); - i++; - } - } - else - { - result.Append(input[i]); - } - } - return result.ToString(); - } - private static string ParseDataDirectory(string configDir, string dataDir) { if (dataDir.StartsWith('"')) { - int lastQuote = -1; - for (int i = dataDir.Length - 1; i > 0; i--) + var original = dataDir; + dataDir = ""; + for (int i = 1; i < original.Length; i++) { - if (dataDir[i] == '"') - { - int ampCount = 0; - int j = i - 1; - while (j >= 0 && dataDir[j] == '&') - { - ampCount++; - j--; - } - if (ampCount % 2 == 0) - { - lastQuote = i; - break; - } - } - } - if (lastQuote > 0) - { - dataDir = dataDir.Substring(0, lastQuote + 1); + if (original[i] == '&') + i++; + else if (original[i] == '"') + break; + dataDir += original[i]; } - dataDir = UnescapeAmpersands(dataDir.Trim('"')); } if (dataDir.StartsWith("?userdata?")) From 3c38bea277870891fb261f72c5ffcac418c88f64 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 08:53:47 -0700 Subject: [PATCH 27/33] CLEANUP: This is probably not actually necessary but it will keep me awake at night --- TES3Merge/Util/Installation.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 9be9eb2..f89deac 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -478,9 +478,6 @@ private static string ParseDataDirectory(string configDir, string dataDir) if (!Path.IsPathRooted(dataDir)) dataDir = Path.GetFullPath(Path.Combine(configDir, dataDir)); - dataDir = dataDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - dataDir = Regex.Replace(dataDir, DuplicateSeparatorPattern, Path.DirectorySeparatorChar.ToString()); - return dataDir; } From 179ad37ce16d13f3db1b3380219f71ecb7b62d3e Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 09:13:34 -0700 Subject: [PATCH 28/33] FIX: Process child configurations only after the curen one has finished. --- TES3Merge/Util/Installation.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index f89deac..c1c2912 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -489,6 +489,7 @@ private void LoadConfiguration(string configDir) throw new Exception("Configuration file does not exist."); } + List subConfigs = new List { }; foreach (var line in File.ReadLines(configPath)) { if (string.IsNullOrEmpty(line) || line.Trim().StartsWith("#")) continue; @@ -519,20 +520,23 @@ private void LoadConfiguration(string configDir) DataLocalDirectory = value; break; case "config": - try - { - LoadConfiguration(ParseDataDirectory(configDir, value)); - } - catch - { - Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + " does not contain an openmw.cfg, skipping"); - } + subConfigs.Add(ParseDataDirectory(configDir, value)); break; case "resources": ResourcesDirectory = ParseDataDirectory(configDir, value); break; } } + + foreach (string config in subConfigs) + try + { + LoadConfiguration(ParseDataDirectory(configDir, config)); + } + catch + { + Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + " does not contain an openmw.cfg, skipping"); + } } /// From dcb2b577d11d1a29dff880ae10ad0c002dbf514b Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 09:34:42 -0700 Subject: [PATCH 29/33] FIX: ParseDataDirectory must be called immediately when adding the data-local directory in order to correctly relativize paths --- TES3Merge/Util/Installation.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index c1c2912..bfd2d6b 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -394,11 +394,10 @@ public OpenMWInstallation(string path) : base(path) if (!string.IsNullOrEmpty(DataLocalDirectory)) { - var realDataDirectory = ParseDataDirectory(path, DataLocalDirectory); - DataDirectories.Add(realDataDirectory); + DataDirectories.Add(DataLocalDirectory); - if (!Directory.Exists(realDataDirectory)) - Directory.CreateDirectory(realDataDirectory); + if (!Directory.Exists(DataLocalDirectory)) + Directory.CreateDirectory(DataLocalDirectory); } if (!string.IsNullOrEmpty(ResourcesDirectory)) @@ -517,7 +516,7 @@ private void LoadConfiguration(string configDir) Archives.Add(value); break; case "data-local": - DataLocalDirectory = value; + DataLocalDirectory = ParseDataDirectory(configDir, value); break; case "config": subConfigs.Add(ParseDataDirectory(configDir, value)); From a86dba28b411655d801e54c9eabe733e37b98e4d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 11:55:33 -0700 Subject: [PATCH 30/33] CLEANUP: Imrpove logging on failures --- TES3Merge/Util/Installation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index bfd2d6b..1e94f42 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -485,7 +485,7 @@ private void LoadConfiguration(string configDir) var configPath = Path.Combine(configDir, "openmw.cfg"); if (!File.Exists(configPath)) { - throw new Exception("Configuration file does not exist."); + throw new Exception("openmw.cfg does not exist at the path " + configPath); } List subConfigs = new List { }; @@ -532,9 +532,9 @@ private void LoadConfiguration(string configDir) { LoadConfiguration(ParseDataDirectory(configDir, config)); } - catch + catch (Exception e) { - Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + " does not contain an openmw.cfg, skipping"); + Util.Logger.WriteLine("WARNING: Sub-configuration " + configDir + " does not contain an openmw.cfg, skipping due to: " + e); } } From a4b166088b47a55a9cb8c71483511ad9b9a95a08 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 11:56:03 -0700 Subject: [PATCH 31/33] FIX: Correct a bug in token replacement where we never actually needed to append path separators manually and broke it because we tried --- TES3Merge/Util/Installation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 1e94f42..74fb230 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -467,9 +467,9 @@ private static string ParseDataDirectory(string configDir, string dataDir) } if (dataDir.StartsWith("?userdata?")) - dataDir = dataDir.Replace("?userdata?", GetDefaultUserDataDirectory() + Path.PathSeparator); + dataDir = dataDir.Replace("?userdata?", GetDefaultUserDataDirectory()); else if (dataDir.StartsWith("?userconfig?")) - dataDir = dataDir.Replace("?userconfig?", GetDefaultConfigurationDirectory() + Path.PathSeparator); + dataDir = dataDir.Replace("?userconfig?", GetDefaultConfigurationDirectory()); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) dataDir = dataDir.Replace('/', '\\'); From 03f950ed2b487c66b4487da425a5dd3ebe46ef51 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 10 Jun 2025 23:21:25 -0700 Subject: [PATCH 32/33] FEAT: Install to the data-local directory if defined --- TES3Merge/Util/Installation.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 74fb230..7628c3b 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -597,12 +597,13 @@ protected override void LoadDataFiles() public override string GetDefaultOutputDirectory() { - if (DataDirectories.Count == 0) - { + // If a data-local directory was defined, always write there to avoid stale files + if (!string.IsNullOrEmpty(DataLocalDirectory)) + return DataDirectories[DataDirectories.Count - 1]; + // Otherwise just use the first data directory. + else if (DataDirectories.Count > 0) + return DataDirectories[0]; + else throw new Exception("No data directories defined. No default output directory could be resolved."); - } - - // Just use the first data directory. - return DataDirectories[0]; } } From 13fec7a0de446312ccd148769c122397687057c4 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 11 Jun 2025 09:35:09 -0700 Subject: [PATCH 33/33] CLEANUP: Neater implementation of GetDefaultOutputDirectory --- TES3Merge/Util/Installation.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/TES3Merge/Util/Installation.cs b/TES3Merge/Util/Installation.cs index 7628c3b..23910a0 100644 --- a/TES3Merge/Util/Installation.cs +++ b/TES3Merge/Util/Installation.cs @@ -597,13 +597,11 @@ protected override void LoadDataFiles() public override string GetDefaultOutputDirectory() { - // If a data-local directory was defined, always write there to avoid stale files - if (!string.IsNullOrEmpty(DataLocalDirectory)) - return DataDirectories[DataDirectories.Count - 1]; - // Otherwise just use the first data directory. - else if (DataDirectories.Count > 0) - return DataDirectories[0]; - else + if (DataDirectories.Count == 0) throw new Exception("No data directories defined. No default output directory could be resolved."); + + var outputDirIndex = string.IsNullOrEmpty(DataLocalDirectory) ? 0 : DataDirectories.Count - 1; + + return DataDirectories[outputDirIndex]; } }