diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20697e169..260584fa8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ All notable changes to this project will be documented in this file.
- [Policy] Fix #3518 rewrite de-indexing policy (#3993 by: JonnyOThan; reviewed: HebaruSan)
- [Netkan] Fix null reference exception in swinfo transformer (#3999 by: HebaruSan)
- [Netkan] Improve netkan relationship error message (#4020, #4021 by: HebaruSan)
+- [Core] Get KSP2 version from game assembly (#4034 by: HebaruSan)
## v1.34.4 (Niven)
diff --git a/Core/CKAN-core.csproj b/Core/CKAN-core.csproj
index ac3d1cd5d..b3f998f10 100644
--- a/Core/CKAN-core.csproj
+++ b/Core/CKAN-core.csproj
@@ -28,6 +28,7 @@
+
diff --git a/Core/Games/KerbalSpaceProgram2.cs b/Core/Games/KerbalSpaceProgram2.cs
index 46c3ff14f..a10029b1a 100644
--- a/Core/Games/KerbalSpaceProgram2.cs
+++ b/Core/Games/KerbalSpaceProgram2.cs
@@ -8,6 +8,7 @@
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Mono.Cecil;
using CKAN.DLC;
using CKAN.Versioning;
@@ -21,7 +22,7 @@ public class KerbalSpaceProgram2 : IGame
public bool GameInFolder(DirectoryInfo where)
=> InstanceAnchorFiles.Any(f => File.Exists(Path.Combine(where.FullName, f)))
- && Directory.Exists(Path.Combine(where.FullName, "KSP2_x64_Data"));
+ && Directory.Exists(Path.Combine(where.FullName, DataDir));
///
/// Get the default non-Steam path to KSP on macOS
@@ -53,7 +54,7 @@ public string PrimaryModDirectory(GameInstance inst)
public string[] StockFolders => new string[]
{
- "KSP2_x64_Data",
+ DataDir,
"MonoBleedingEdge",
"PDLauncher",
};
@@ -84,8 +85,7 @@ public bool AllowInstallationIn(string name, out string path)
public void RebuildSubdirectories(string absGameRoot)
{
- const string dataDir = "KSP2_x64_Data";
- var path = Path.Combine(absGameRoot, dataDir);
+ var path = Path.Combine(absGameRoot, DataDir);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
@@ -151,17 +151,39 @@ public GameVersion[] ParseBuildsJson(JToken json)
=> json.ToObject();
public GameVersion DetectVersion(DirectoryInfo where)
- => VersionFromFile(Path.Combine(where.FullName, "KSP2_x64.exe"));
-
- private GameVersion VersionFromFile(string path)
- => File.Exists(path)
- && GameVersion.TryParse(FileVersionInfo.GetVersionInfo(path).ProductVersion
+ => VersionFromAssembly(Path.Combine(where.FullName,
+ DataDir,
+ "Managed",
+ "Assembly-CSharp.dll"))
+ ?? VersionFromExecutable(Path.Combine(where.FullName,
+ "KSP2_x64.exe"))
+ // Fall back to the most recent version
+ ?? KnownVersions.Last();
+
+ private static GameVersion VersionFromAssembly(string assemblyPath)
+ => File.Exists(assemblyPath)
+ && GameVersion.TryParse(
+ AssemblyDefinition.ReadAssembly(assemblyPath)
+ .Modules
+ .SelectMany(m => m.GetTypes())
+ .Where(t => t.Name == "VersionID")
+ .SelectMany(t => t.Fields)
+ .Where(f => f.Name == "VERSION_TEXT")
+ .Select(f => (string)f.Constant)
+ .Select(ver => string.Join(".", ver.Split('.').Take(4)))
+ .FirstOrDefault(),
+ out GameVersion v)
+ ? v
+ : null;
+
+ private static GameVersion VersionFromExecutable(string exePath)
+ => File.Exists(exePath)
+ && GameVersion.TryParse(FileVersionInfo.GetVersionInfo(exePath).ProductVersion
// Fake instances have an EXE containing just the version string
- ?? File.ReadAllText(path),
+ ?? File.ReadAllText(exePath),
out GameVersion v)
? v
- // Fall back to the most recent version
- : KnownVersions.Last();
+ : null;
public string CompatibleVersionsFile => "compatible_game_versions.json";
@@ -185,6 +207,8 @@ private GameVersion VersionFromFile(string path)
public Uri MetadataBugtrackerURL => new Uri("https://github.com/KSP-CKAN/KSP2-NetKAN/issues/new/choose");
+ private const string DataDir = "KSP2_x64_Data";
+
// Key: Allowed value of install_to
// Value: Relative path
// (PrimaryModDirectoryRelative is allowed implicitly)