forked from KSP-CKAN/CKAN-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KSP.cs
376 lines (305 loc) · 11.7 KB
/
KSP.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Transactions;
using log4net;
namespace CKAN
{
/// <summary>
/// Everything for dealing with KSP itself.
/// </summary>
public class KSP
{
public IUser User { get; set; }
#region Fields and Properties
private static readonly ILog log = LogManager.GetLogger(typeof(KSP));
private readonly string gamedir;
private KSPVersion version;
public NetFileCache Cache { get; private set; }
public RegistryManager RegistryManager
{
get { return RegistryManager.Instance(this); }
}
public Registry Registry
{
get { return RegistryManager.registry; }
}
#endregion
#region Construction and Initialisation
/// <summary>
/// Returns a KSP object, insisting that directory contains a valid KSP install.
/// Will initialise a CKAN instance in the KSP dir if it does not already exist.
/// Throws a NotKSPDirKraken if directory is not a KSP install.
/// </summary>
public KSP(string directory, IUser user)
{
User = user;
// Make sure our path is absolute and has normalised slashes.
directory = KSPPathUtils.NormalizePath(Path.GetFullPath(directory));
if (! IsKspDir(directory))
{
throw new NotKSPDirKraken(directory);
}
gamedir = directory;
Init();
Cache = new NetFileCache(DownloadCacheDir());
}
/// <summary>
/// Create the CKAN directory and any supporting files.
/// </summary>
private void Init()
{
log.DebugFormat("Initialising {0}", CkanDir());
if (! Directory.Exists(CkanDir()))
{
User.RaiseMessage("Setting up CKAN for the first time...");
User.RaiseMessage("Creating {0}", CkanDir());
Directory.CreateDirectory(CkanDir());
User.RaiseMessage("Scanning for installed mods...");
ScanGameData();
}
if (! Directory.Exists(DownloadCacheDir()))
{
User.RaiseMessage("Creating {0}", DownloadCacheDir());
Directory.CreateDirectory(DownloadCacheDir());
}
// Clear any temporary files we find. If the directory
// doesn't exist, then no sweat; FilesystemTransaction
// will auto-create it as needed.
// Create our temporary directories, or clear them if they
// already exist.
if (Directory.Exists(TempDir()))
{
var directory = new DirectoryInfo(TempDir());
foreach (FileInfo file in directory.GetFiles()) file.Delete();
foreach (DirectoryInfo subDirectory in directory.GetDirectories()) subDirectory.Delete(true);
}
log.DebugFormat("Initialised {0}", CkanDir());
}
#endregion
#region KSP Directory Detection and Versioning
/// <summary>
/// Returns the path to our portable version of KSP if ckan.exe is in the same
/// directory as the game. Otherwise, returns null.
/// </summary>
public static string PortableDir()
{
// Find the directory our executable is stored in.
// In Perl, this is just `use FindBin qw($Bin);` Verbose enough, C#?
string exe_dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
log.DebugFormat("Checking if KSP is in my exe dir: {0}", exe_dir);
// Checking for a GameData directory probably isn't the best way to
// detect KSP, but it works. More robust implementations welcome.
if (IsKspDir(exe_dir))
{
log.InfoFormat("KSP found at {0}", exe_dir);
return exe_dir;
}
return null;
}
/// <summary>
/// Attempts to automatically find a KSP install on this system.
/// Returns the path to the install on success.
/// Throws a DirectoryNotFoundException on failure.
/// </summary>
public static string FindGameDir()
{
// See if we can find KSP as part of a Steam install.
string steam = KSPPathUtils.SteamPath();
if (steam != null)
{
string ksp_dir = Path.Combine(steam, KSPManager.steamKSP);
if (Directory.Exists(ksp_dir) && IsKspDir(ksp_dir))
{
log.InfoFormat("KSP found at {0}", ksp_dir);
return ksp_dir;
}
log.DebugFormat("Have Steam, but KSP is not at {0}", ksp_dir);
}
// Oh noes! We can't find KSP!
throw new DirectoryNotFoundException();
}
/// <summary>
/// Checks if the specified directory looks like a KSP directory.
/// Returns true if found, false if not.
/// </summary>
internal static bool IsKspDir(string directory)
{
//first we need to check is directory exists
if (!Directory.Exists(Path.Combine(directory, "GameData")))
{
log.DebugFormat("Cannot find GameData in {0}", directory);
return false;
}
if (!File.Exists(Path.Combine(directory, "readme.txt")))
{
log.DebugFormat("Cannot find readme in {0}", directory);
return false;
}
//If both exist we should be able to get game version
try
{
DetectVersion(directory);
}
catch (NotKSPDirKraken)
{
log.DebugFormat("Cannot detect KSP version in {0}", directory);
return false;
}
log.DebugFormat("{0} looks like a GameDir", directory);
return true;
}
/// <summary>
/// Detects the version of KSP in a given directory.
/// Throws a NotKSPDirKraken if anything goes wrong.
/// </summary>
private static KSPVersion DetectVersion(string directory)
{
//Contract.Requires<ArgumentNullException>(directory==null);
string readme;
try
{
// Slurp our README into memory
readme = File.ReadAllText(Path.Combine(directory, "readme.txt"));
}
catch
{
log.Error("Could not open KSP readme.txt in "+directory);
throw new NotKSPDirKraken("readme.txt not found or not readable");
}
// And find the KSP version. Easy! :)
Match match = Regex.Match(readme, @"^Version\s+(\d+\.\d+\.\d+)",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (match.Success)
{
string version = match.Groups[1].Value;
log.DebugFormat("Found version {0}", version);
return new KSPVersion(version);
}
// Oh noes! We couldn't find the version!
log.Error("Could not find KSP version in readme.txt");
throw new NotKSPDirKraken(directory, "Could not find KSP version in readme.txt");
}
#endregion
#region Things which would be better as Properties
public string GameDir()
{
return gamedir;
}
public string GameData()
{
return Path.Combine(GameDir(), "GameData");
}
public string CkanDir()
{
return Path.Combine(GameDir(), "CKAN");
}
public string DownloadCacheDir()
{
return Path.Combine(CkanDir(), "downloads");
}
public string Ships()
{
return Path.Combine(GameDir(), "Ships");
}
public string Tutorial()
{
return Path.Combine(GameDir(), "saves", "training");
}
public string TempDir()
{
return Path.Combine(CkanDir(), "temp");
}
public KSPVersion Version()
{
if (version != null)
{
return version;
}
return version = DetectVersion(GameDir());
}
#endregion
#region CKAN/GameData Directory Maintenance
/// <summary>
/// Removes all files from the download (cache) directory.
/// </summary>
public void CleanCache()
{
log.Debug("Cleaning cahce directory");
string[] files = Directory.GetFiles(DownloadCacheDir(), "*", SearchOption.AllDirectories);
foreach (string file in files)
{
if (Directory.Exists(file))
{
log.DebugFormat("Skipping directory: {0}", file);
continue;
}
log.DebugFormat("Deleting {0}", file);
File.Delete(file);
}
}
/// <summary>
/// Clears the registry of DLL data, and refreshes it by scanning GameData.
/// This operates as a transaction.
/// This *saves* the registry upon completion.
/// </summary>
// TODO: This would likely be better in the Registry class itself.
public void ScanGameData()
{
using (TransactionScope tx = CkanTransaction.CreateTransactionScope())
{
Registry.ClearDlls();
// TODO: It would be great to optimise this to skip .git directories and the like.
// Yes, I keep my GameData in git.
// Alas, EnumerateFiles is *case-sensitive* in its pattern, which causes
// DLL files to be missed under Linux; we have to pick .dll, .DLL, or scanning
// GameData *twice*.
//
// The least evil is to walk it once, and filter it ourselves.
IEnumerable<string> files = Directory.EnumerateFiles(
GameData(),
"*",
SearchOption.AllDirectories
);
files = files.Where(file => Regex.IsMatch(file, @"\.dll$", RegexOptions.IgnoreCase));
foreach (string dll in files.Select(KSPPathUtils.NormalizePath))
{
Registry.RegisterDll(this, dll);
}
tx.Complete();
}
RegistryManager.Save();
}
#endregion
/// <summary>
/// Returns path relative to this KSP's GameDir.
/// </summary>
public string ToRelativeGameDir(string path)
{
return KSPPathUtils.ToRelative(path, GameDir());
}
/// <summary>
/// Given a path relative to this KSP's GameDir, returns the
/// absolute path on the system.
/// </summary>
public string ToAbsoluteGameDir(string path)
{
return KSPPathUtils.ToAbsolute(path, GameDir());
}
public override string ToString()
{
return "KSP Install:" + gamedir;
}
public override bool Equals(object obj)
{
var other = obj as KSP;
return other != null ? gamedir.Equals(other.GameDir()) : base.Equals(obj);
}
public override int GetHashCode()
{
return gamedir.GetHashCode();
}
}
}