diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62920c3a7d..4069a7f3c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
- [Netkan] Coerce GitHub URLs into the authenticated API in Netkan (#2946 by: HebaruSan; reviewed: DasSkelett)
- [Netkan] Request fewer GitHub releases and cache string URLs in Netkan (#2950 by: HebaruSan)
- [GUI] Create ~/.local/share/applications/ if it doesn't exist on Linux (#1848 by: DinCahill; reviewed: ayan4ml, politas, dannydi12)
+- [Netkan] Catch nested GameData folders in Netkan (#2948 by: HebaruSan; reviewed: techman83)
## v1.26.6 (Leonov)
diff --git a/Netkan/ConsoleUser.cs b/Netkan/ConsoleUser.cs
index 4540d88520..a8cec710e3 100644
--- a/Netkan/ConsoleUser.cs
+++ b/Netkan/ConsoleUser.cs
@@ -20,7 +20,7 @@ public class ConsoleUser : IUser
///
/// Initializes a new instance of the class.
///
- /// If set to true, supress interactive dialogs like Yes/No-Dialog or SelectionDialog.
+ /// If set to true, suppress interactive dialogs like Yes/No-Dialog or SelectionDialog.
public ConsoleUser (bool headless)
{
Headless = headless;
diff --git a/Netkan/Services/IModuleService.cs b/Netkan/Services/IModuleService.cs
index 1ac9302051..062e10fc02 100644
--- a/Netkan/Services/IModuleService.cs
+++ b/Netkan/Services/IModuleService.cs
@@ -13,5 +13,6 @@ internal interface IModuleService
IEnumerable GetConfigFiles(CkanModule module, ZipFile zip);
+ IEnumerable FileDestinations(CkanModule module, string filePath);
}
}
diff --git a/Netkan/Services/ModuleService.cs b/Netkan/Services/ModuleService.cs
index 6303933d79..7c4f3a30ab 100644
--- a/Netkan/Services/ModuleService.cs
+++ b/Netkan/Services/ModuleService.cs
@@ -72,6 +72,13 @@ public IEnumerable GetConfigFiles(CkanModule module, ZipFile zi
.Where(instF => cfgRegex.IsMatch(instF.source.Name));
}
+ public IEnumerable FileDestinations(CkanModule module, string filePath)
+ {
+ return ModuleInstaller
+ .FindInstallableFiles(module, filePath, new KSP("/", "dummy", null, false))
+ .Select(f => f.destination);
+ }
+
///
/// Return a parsed JObject from a stream.
///
diff --git a/Netkan/Validators/InstallsFilesValidator.cs b/Netkan/Validators/InstallsFilesValidator.cs
index 5a404126ac..a03b346eeb 100644
--- a/Netkan/Validators/InstallsFilesValidator.cs
+++ b/Netkan/Validators/InstallsFilesValidator.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using CKAN.NetKAN.Model;
using CKAN.NetKAN.Services;
@@ -20,7 +21,6 @@ public void Validate(Metadata metadata)
var file = _http.DownloadPackage(metadata.Download, metadata.Identifier, metadata.RemoteTimestamp);
// Make sure this would actually generate an install.
-
if (!_moduleService.HasInstallableFiles(mod, file))
{
throw new Kraken(string.Format(
@@ -28,6 +28,16 @@ public void Validate(Metadata metadata)
mod.DescribeInstallStanzas()
));
}
+
+ // Make sure no paths include GameData other than at the start
+ var gamedatas = _moduleService.FileDestinations(mod, file)
+ .Where(p => p.StartsWith("GameData") && p.LastIndexOf("/GameData/") > 0)
+ .ToList();
+ if (gamedatas.Any())
+ {
+ var badPaths = string.Join("\r\n", gamedatas);
+ throw new Kraken($"GameData directory found within GameData:\r\n{badPaths}");
+ }
}
}
}
diff --git a/Netkan/Validators/LicensesValidator.cs b/Netkan/Validators/LicensesValidator.cs
index fdfe90e3af..a46e2e01ed 100644
--- a/Netkan/Validators/LicensesValidator.cs
+++ b/Netkan/Validators/LicensesValidator.cs
@@ -33,7 +33,7 @@ public void Validate(Metadata metadata)
{
if (licenses == null || licenses.Count < 1)
{
- throw new Kraken("License should match spec. Set `x_netkan_license_ok` to supress");
+ throw new Kraken("License should match spec. Set `x_netkan_license_ok` to suppress");
}
else foreach (var lic in licenses)
{
@@ -44,7 +44,7 @@ public void Validate(Metadata metadata)
}
catch
{
- throw new Kraken($"License {lic} should match spec. Set `x_netkan_license_ok` to supress");
+ throw new Kraken($"License {lic} should match spec. Set `x_netkan_license_ok` to suppress");
}
}
}