• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 17904669173

22 Sep 2025 04:34AM UTC coverage: 75.604% (+1.2%) from 74.397%
17904669173

push

github

HebaruSan
Merge #4443 Report number of filtered files in install

5231 of 7236 branches covered (72.29%)

Branch coverage included in aggregate %.

192 of 218 new or added lines in 41 files covered. (88.07%)

35 existing lines in 7 files now uncovered.

11163 of 14448 relevant lines covered (77.26%)

1.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.94
/Core/Games/KerbalSpaceProgram2.cs
1
using System;
2
using System.Diagnostics;
3
using System.Linq;
4
using System.IO;
5
using System.Collections.Generic;
6
using System.Reflection;
7
using System.Diagnostics.CodeAnalysis;
8

9
using log4net;
10
using Newtonsoft.Json;
11
using Newtonsoft.Json.Linq;
12
using Mono.Cecil;
13

14
using CKAN.IO;
15
using CKAN.DLC;
16
using CKAN.Versioning;
17
using CKAN.Extensions;
18

19
namespace CKAN.Games.KerbalSpaceProgram2
20
{
21
    public class KerbalSpaceProgram2 : IGame
22
    {
23
        public string ShortName => "KSP2";
2✔
24
        public DateTime FirstReleaseDate => new DateTime(2023, 2, 24);
×
25

26
        public bool GameInFolder(DirectoryInfo where)
27
            => InstanceAnchorFiles.Any(f => File.Exists(Path.Combine(where.FullName, f)))
2!
28
                && Directory.Exists(Path.Combine(where.FullName, DataDir));
29

30
        /// <summary>
31
        /// Get the default non-Steam path to KSP on macOS
32
        /// </summary>
33
        /// <returns>
34
        /// "/Applications/Kerbal Space Program" if it exists and we're on a Mac, else null
35
        /// </returns>
36
        public DirectoryInfo? MacPath()
37
        {
2✔
38
            if (Platform.IsMac)
2!
39
            {
×
40
                string installPath = Path.Combine(
×
41
                    // This is "/Applications" in Mono on Mac
42
                    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
43
                    "Kerbal Space Program 2"
44
                );
45
                return Directory.Exists(installPath) ? new DirectoryInfo(installPath)
×
46
                                                     : null;
47
            }
48
            return null;
2✔
49
        }
2✔
50

UNCOV
51
        public string PrimaryModDirectoryRelative => "GameData/Mods";
×
52
        public string[] AlternateModDirectoriesRelative => new string[] { "BepInEx/plugins" };
×
53

54
        public string PrimaryModDirectory(GameInstance inst)
55
            => CKANPathUtils.NormalizePath(
×
56
                Path.Combine(inst.GameDir, PrimaryModDirectoryRelative));
57

58
        public string[] StockFolders => new string[]
×
59
        {
60
            DataDir,
61
            "MonoBleedingEdge",
62
            "PDLauncher",
63
        };
64

65
        public string[] LeaveEmptyInClones => new string[]
×
66
        {
67
            "CKAN/history",
68
            "CKAN/downloads",
69
        };
70

UNCOV
71
        public string[] ReservedPaths => Array.Empty<string>();
×
72

UNCOV
73
        public string[] CreateableDirs => new string[]
×
74
        {
75
            "GameData",
76
            "GameData/Mods",
77
            "BepInEx",
78
            "BepInEx/plugins",
79
        };
80

81
        public string[] AutoRemovableDirs => Array.Empty<string>();
×
82

83
        /// <summary>
84
        /// Checks the path against a list of reserved game directories
85
        /// </summary>
86
        /// <param name="inst">Game instance we're checking</param>
87
        /// <param name="path">Path to check</param>
88
        /// <returns>True if reserved, false otherwise</returns>
89
        public bool IsReservedDirectory(GameInstance inst, string path)
NEW
90
            => path == inst.GameDir
×
91
               || path == inst.CkanDir
92
               || path == PrimaryModDirectory(inst)
93
               || PluginsPieces.Accumulate(inst.GameDir, Path.Combine)
94
                               .Select(CKANPathUtils.NormalizePath)
95
                               .Contains(path);
96

97
        public bool AllowInstallationIn(string name, [NotNullWhen(returnValue: true)] out string? path)
UNCOV
98
            => allowedFolders.TryGetValue(name, out path);
×
99

100
        public void RebuildSubdirectories(string absGameRoot)
101
        {
×
102
            // Create the plugin path used by Planety
103
            foreach (var path in PluginsPieces.Accumulate(absGameRoot, Path.Combine))
×
104
            {
×
105
                if (!Directory.Exists(path))
×
106
                {
×
107
                    Directory.CreateDirectory(path);
×
108
                }
×
109
            }
×
110
        }
×
111

112
        public string[] DefaultCommandLines(SteamLibrary steamLib, DirectoryInfo path)
113
            => Enumerable.Repeat(Platform.IsMac
×
114
                                     ? "./KSP2.app/Contents/MacOS/KSP2"
115
                                     : string.Format(Platform.IsUnix ? "./{0} -single-instance"
116
                                                                     : "{0} -single-instance",
117
                                                     InstanceAnchorFiles.FirstOrDefault(f =>
118
                                                         File.Exists(Path.Combine(path.FullName, f)))
×
119
                                                     ?? InstanceAnchorFiles.First()),
120
                                 1)
121
                         .Concat(steamLib.GameAppURLs(path)
122
                                         .Select(url => url.ToString()))
×
123
                         .ToArray();
124

125
        public string[] AdjustCommandLine(string[] args, GameVersion? installedVersion)
126
            => args;
×
127

128
        public IDlcDetector[] DlcDetectors => Array.Empty<IDlcDetector>();
×
129

130
        public IDictionary<string, string[]> InstallFilterPresets =>
131
            new Dictionary<string, string[]>();
×
132

133
        private static readonly Uri BuildMapUri =
2✔
134
            new Uri("https://raw.githubusercontent.com/KSP-CKAN/KSP2-CKAN-meta/main/builds.json");
135
        private static readonly string cachedBuildMapPath =
2✔
136
            Path.Combine(CKANPathUtils.AppDataPath, "builds-ksp2.json");
137

138
        private List<GameVersion> versions =
2✔
139
            JsonConvert.DeserializeObject<List<GameVersion>>(
140
                File.Exists(cachedBuildMapPath)
141
                    ? File.ReadAllText(cachedBuildMapPath)
142
                    : Assembly.GetExecutingAssembly()
143
                              .GetManifestResourceStream("CKAN.builds-ksp2.json")
144
                          is Stream s
145
                        ? new StreamReader(s).ReadToEnd()
146
                        : "")
147
            ?? new List<GameVersion>();
148

149
        public void RefreshVersions(string? userAgent)
150
        {
×
151
            try
152
            {
×
153
                if (Net.DownloadText(BuildMapUri, userAgent) is string json)
×
154
                {
×
155
                    versions = JsonConvert.DeserializeObject<List<GameVersion>>(json) ?? versions;
×
156

157
                    // Save to disk if download and parse succeeds
158
                    new FileInfo(cachedBuildMapPath).Directory?.Create();
×
159
                    json.WriteThroughTo(cachedBuildMapPath);
×
160
                }
×
161
            }
×
162
            catch (Exception e)
×
163
            {
×
164
                log.WarnFormat("Could not retrieve latest build map from: {0}", BuildMapUri);
×
165
                log.Debug(e);
×
166
            }
×
167
        }
×
168

169
        public List<GameVersion> KnownVersions => versions;
×
170

171
        public GameVersion[] EmbeddedGameVersions
172
            => (Assembly.GetExecutingAssembly()
×
173
                        .GetManifestResourceStream("CKAN.builds-ksp2.json")
174
                is Stream s
175
                    ? JsonConvert.DeserializeObject<GameVersion[]>(new StreamReader(s).ReadToEnd())
176
                    : null)
177
                ?? Array.Empty<GameVersion>();
178

179
        public GameVersion[] ParseBuildsJson(JToken json)
180
            => json.ToObject<GameVersion[]>()
×
181
                ?? Array.Empty<GameVersion>();
182

183
        public GameVersion DetectVersion(DirectoryInfo where)
184
            => VersionFromAssembly(Path.Combine(where.FullName,
×
185
                                                DataDir,
186
                                                "Managed",
187
                                                "Assembly-CSharp.dll"))
188
                ?? VersionFromExecutable(Path.Combine(where.FullName,
189
                                                      "KSP2_x64.exe"))
190
                // Fall back to the most recent version
191
                ?? KnownVersions.Last();
192

193
        private static GameVersion? VersionFromAssembly(string assemblyPath)
194
            => File.Exists(assemblyPath)
×
195
                && GameVersion.TryParse(
196
                    AssemblyDefinition.ReadAssembly(assemblyPath)
197
                                      .Modules
198
                                      .SelectMany(m => m.GetTypes())
×
199
                                      .Where(t => t.Name == "VersionID")
×
200
                                      .SelectMany(t => t.Fields)
×
201
                                      .Where(f => f.Name == "VERSION_TEXT")
×
202
                                      .Select(f => (string)f.Constant)
×
203
                                      .Select(ver => string.Join(".", ver.Split('.').Take(4)))
×
204
                                      .FirstOrDefault(),
205
                    out GameVersion? v)
206
                        ? v
207
                        : null;
208

209
        private static GameVersion? VersionFromExecutable(string exePath)
210
            => File.Exists(exePath)
×
211
                && GameVersion.TryParse(FileVersionInfo.GetVersionInfo(exePath).ProductVersion
212
                                        // Fake instances have an EXE containing just the version string
213
                                        ?? File.ReadAllText(exePath),
214
                                        out GameVersion? v)
215
                    ? v
216
                    : null;
217

218
        public GameVersion[] DefaultCompatibleVersions(GameVersion installedVersion)
219
            // KSP2 didn't last long enough to break compatibility :~(
220
            => Enumerable.Range(1, 2)
2✔
221
                         .Select(minor => new GameVersion(0, minor))
2✔
222
                         .ToArray();
223

224
        public string CompatibleVersionsFile => "compatible_game_versions.json";
×
225

226
        public string[] InstanceAnchorFiles =>
227
            Platform.IsUnix
2!
228
                ? new string[]
229
                {
230
                    // Native Linux port, if/when it arrives
231
                    "KSP2.x86_64",
232
                    // Windows EXE via Proton on Linux
233
                    "KSP2_x64.exe",
234
                }
235
                : new string[]
236
                {
237
                    "KSP2_x64.exe",
238
                };
239

240
        public Uri DefaultRepositoryURL => new Uri("https://github.com/KSP-CKAN/KSP2-CKAN-meta/archive/main.tar.gz");
×
241

242
        public Uri RepositoryListURL => new Uri("https://raw.githubusercontent.com/KSP-CKAN/KSP2-CKAN-meta/main/repositories.json");
×
243

244
        public Uri MetadataBugtrackerURL => new Uri("https://github.com/KSP-CKAN/KSP2-NetKAN/issues/new/choose");
×
245

246
        public Uri ModSupportURL => new Uri("https://forum.kerbalspaceprogram.com/forum/137-ksp2-technical-support-pc-modded-installs/");
×
247

248
        private const string DataDir = "KSP2_x64_Data";
249

250
        private static readonly string[] PluginsPieces = { DataDir, "Plugins", "x86_64" };
2✔
251

252
        // Key: Allowed value of install_to
253
        // Value: Relative path
254
        // (PrimaryModDirectoryRelative is allowed implicitly)
255
        private readonly Dictionary<string, string> allowedFolders = new Dictionary<string, string>
2✔
256
        {
257
            { "BepInEx",         "BepInEx"         },
258
            { "BepInEx/plugins", "BepInEx/plugins" },
259
        };
260

261
        private static readonly ILog log = LogManager.GetLogger(typeof(KerbalSpaceProgram2));
2✔
262
    }
263
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc