diff --git a/Core/GameInstance.cs b/Core/GameInstance.cs index 2b375748f2..2fd5b6fe7e 100644 --- a/Core/GameInstance.cs +++ b/Core/GameInstance.cs @@ -341,7 +341,7 @@ public bool Scan() // The least evil is to walk it once, and filter it ourselves. IEnumerable files = Directory .EnumerateFiles(game.PrimaryModDirectory(this), "*", SearchOption.AllDirectories) - .Where(file => dllRegex.IsMatch(file)) + .Where(file => file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase)) .Select(CKANPathUtils.NormalizePath) .Where(absPath => !game.StockFolders.Any(f => ToRelativeGameDir(absPath).StartsWith($"{f}/"))); @@ -365,8 +365,6 @@ public bool Scan() } } - private static readonly Regex dllRegex = new Regex(@"\.dll$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - #endregion /// diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index 1511348796..8258d98d94 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -333,6 +333,7 @@ private static void CheckKindInstallationKraken(CkanModule module) /// /// Installs the module from the zipfile provided. /// Returns a list of files installed. + /// Propagates a DllLocationMismatchKraken if the user has a bad manual install. /// Propagates a BadMetadataKraken if our install metadata is bad. /// Propagates a CancelledActionKraken if the user decides not to overwite unowned files. /// Propagates a FileExistsKraken if we were going to overwrite a file. @@ -348,9 +349,27 @@ private IEnumerable InstallModule(CkanModule module, string zip_filename try { var dll = registry.DllPath(module.identifier); - if (dll != null && !files.Any(f => ksp.ToRelativeGameDir(f.destination) == dll)) + if (!string.IsNullOrEmpty(dll)) { - throw new DllLocationMismatchKraken(dll, $"DLL for module {module.identifier} found at {dll}, but it's not where CKAN would install it. Aborting to prevent multiple copies of the same mod being installed. To install this module, uninstall it manually and try again."); + // Find where we're installing identifier.optionalversion.dll + // (file name might not be an exact match with manually installed) + var dllFolders = files.Where(f => + Path.GetFileName(f.destination).StartsWith(module.identifier) + && f.destination.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase)) + .Select(f => Path.GetDirectoryName(ksp.ToRelativeGameDir(f.destination))) + .ToHashSet(); + if (!dllFolders.Contains(Path.GetDirectoryName(dll))) + { + // Manually installed DLL is somewhere else where we're not installing files, + // probable bad install, alert user and abort + throw new DllLocationMismatchKraken(dll, $"DLL for module {module.identifier} found at {dll}, but it's not where CKAN would install it. Aborting to prevent multiple copies of the same mod being installed. To install this module, uninstall it manually and try again."); + } + // Delete the manually installed DLL transaction-style because we believe we'll be replacing it + var toDelete = ksp.ToAbsoluteGameDir(dll); + log.DebugFormat("Deleting manually installed DLL {0}", toDelete); + TxFileManager file_transaction = new TxFileManager(); + file_transaction.Snapshot(toDelete); + file_transaction.Delete(toDelete); } // Look for overwritable files if session is interactive @@ -474,14 +493,10 @@ private bool StreamsEqual(Stream s1, Stream s2) private void DeleteConflictingFiles(IEnumerable files) { TxFileManager file_transaction = new TxFileManager(); - using (var transaction = CkanTransaction.CreateTransactionScope()) + foreach (InstallableFile file in files) { - foreach (InstallableFile file in files) - { - log.DebugFormat("Trying to delete {0}", file.destination); - file_transaction.Delete(file.destination); - } - transaction.Complete(); + log.DebugFormat("Trying to delete {0}", file.destination); + file_transaction.Delete(file.destination); } } @@ -585,8 +600,8 @@ internal static void CopyZipEntry(ZipFile zipfile, ZipEntry entry, string fullPa } // Snapshot whatever was there before. If there's nothing, this will just - // remove our file on rollback. We still need this even thought we won't - // overwite files, as it ensures deletiion on rollback. + // remove our file on rollback. We still need this even though we won't + // overwite files, as it ensures deletion on rollback. file_transaction.Snapshot(fullPath); try diff --git a/Core/Registry/Registry.cs b/Core/Registry/Registry.cs index 05b7c12e7b..3eb35f8fbe 100644 --- a/Core/Registry/Registry.cs +++ b/Core/Registry/Registry.cs @@ -836,9 +836,9 @@ public void RegisterDll(GameInstance ksp, string absolute_path) // http://xkcd.com/208/ // This regex works great for things like GameData/Foo/Foo-1.2.dll - Match match = Regex.Match( - relative_path, @" - ^GameData/ # DLLs only live in GameData + Match match = Regex.Match(relative_path, + // DLLs only live in the primary mod directory + $"^{ksp.game.PrimaryModDirectoryRelative}/" + @" (?:.*/)? # Intermediate paths (ending with /) (?[^.]+) # Our DLL name, up until the first dot. .*\.dll$ # Everything else, ending in dll