-
-
Notifications
You must be signed in to change notification settings - Fork 348
/
Main.cs
370 lines (317 loc) · 13.8 KB
/
Main.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
// Reference CKAN client
// Paul '@pjf' Fenwick
//
// License: CC-BY 4.0, LGPL, or MIT (your choice)
using System;
using System.Net;
using System.Diagnostics;
using System.Linq;
#if WINDOWS && NET5_0_OR_GREATER
using System.Runtime.Versioning;
#endif
using Autofac;
using log4net;
using log4net.Core;
namespace CKAN.CmdLine
{
internal class MainClass
{
private static readonly ILog log = LogManager.GetLogger(typeof (MainClass));
/*
* When the STAThread is applied, it changes the apartment state of the current thread to be single threaded.
* Without getting into a huge discussion about COM and threading,
* this attribute ensures the communication mechanism between the current thread an
* other threads that may want to talk to it via COM. When you're using Windows Forms,
* depending on the feature you're using, it may be using COM interop in order to communicate with
* operating system components. Good examples of this are the Clipboard and the File Dialogs.
*/
[STAThread]
public static int Main(string[] args)
{
// Launch debugger if the "--debugger" flag is present in the command line arguments.
// We want to do this as early as possible so just check the flag manually, rather than doing the
// more robust argument parsing.
if (args.Any(i => i == "--debugger"))
{
Debugger.Launch();
}
// Default to GUI if there are no command line args or if the only args are flags rather than commands.
if (args.All(a => a is "--verbose"
or "--debug"
or "--asroot"
or "--show-console"))
{
var guiCommand = args.ToList();
guiCommand.Insert(0, "gui");
args = guiCommand.ToArray();
}
Logging.Initialize();
// We need to load the game instance manager before parsing the args,
// which is too late for debug flags if we want them active during the instance loading
if (args.Contains("--debug"))
{
LogManager.GetRepository().Threshold = Level.Debug;
log.Info("Debug logging enabled");
}
else if (args.Contains("--verbose"))
{
LogManager.GetRepository().Threshold = Level.Info;
log.Info("Verbose logging enabled");
}
log.Info("CKAN started.");
// Force-allow TLS 1.2 for HTTPS URLs, because GitHub requires it.
// This is on by default in .NET 4.6, but not in 4.5.
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
try
{
return Execute(null, null, args);
}
finally
{
RegistryManager.DisposeAll();
}
}
public static int Execute(GameInstanceManager? manager, CommonOptions? opts, string[] args)
{
var repoData = ServiceLocator.Container.Resolve<RepositoryDataManager>();
// We shouldn't instantiate Options if it's a subcommand.
// It breaks command-specific help, for starters.
try
{
if (args.Length > 0)
{
switch (args[0])
{
case "repair":
return (new Repair(repoData)).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "instance":
return (new GameInstance()).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "compat":
return (new Compat()).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "repo":
return (new Repo(repoData)).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "authtoken":
return (new AuthToken()).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "cache":
return (new Cache()).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "mark":
return (new Mark(repoData)).RunSubCommand(manager, opts, new SubCommandOptions(args));
case "filter":
return (new Filter()).RunSubCommand(manager, opts, new SubCommandOptions(args));
}
}
}
catch (NoGameInstanceKraken)
{
log.Info("CKAN exiting.");
return printMissingInstanceError(new ConsoleUser(false));
}
Options cmdline;
try
{
cmdline = new Options(args);
}
catch (BadCommandKraken)
{
log.Info("CKAN exiting.");
return AfterHelp();
}
// Process commandline options.
CommonOptions options = (CommonOptions)cmdline.options;
options.Merge(opts);
IUser user = new ConsoleUser(options.Headless);
if (manager == null)
{
manager = new GameInstanceManager(user);
}
else
{
manager.User = user;
}
try
{
int exitCode = options.Handle(manager, user);
if (exitCode != Exit.OK)
{
return exitCode;
}
// Don't bother with instances or registries yet because some commands don't need them.
return RunSimpleAction(cmdline, options, args, user, manager);
}
finally
{
log.Info("CKAN exiting.");
}
}
public static int AfterHelp()
{
// Our help screen will already be shown. Let's add some extra data.
new ConsoleUser(false).RaiseMessage(
Properties.Resources.MainVersion, Meta.GetVersion(VersionFormat.Full));
return Exit.BADOPT;
}
public static CKAN.GameInstance GetGameInstance(GameInstanceManager? manager)
{
var inst = manager?.CurrentInstance
?? manager?.GetPreferredInstance();
#pragma warning disable IDE0270
if (inst == null)
{
throw new NoGameInstanceKraken();
}
#pragma warning restore IDE0270
return inst;
}
/// <summary>
/// Run whatever action the user has provided
/// </summary>
/// <returns>The exit status that should be returned to the system.</returns>
private static int RunSimpleAction(Options cmdline, CommonOptions options, string[] args, IUser user, GameInstanceManager manager)
{
var repoData = ServiceLocator.Container.Resolve<RepositoryDataManager>();
try
{
switch (cmdline.action)
{
#if NETFRAMEWORK || WINDOWS
case "gui":
#if NET6_0_OR_GREATER
if (Platform.IsWindows)
{
#endif
return Gui(manager, (GuiOptions)options, args);
#if NET6_0_OR_GREATER
}
else
{
return Exit.ERROR;
}
#else
#endif
#endif
case "consoleui":
return ConsoleUi(manager, (ConsoleUIOptions)options);
case "prompt":
return new Prompt(manager, repoData).RunCommand(cmdline.options);
case "version":
return Version(user);
case "update":
return (new Update(repoData, user, manager)).RunCommand(cmdline.options);
case "available":
return (new Available(repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "install":
Scan(GetGameInstance(manager), user, cmdline.action);
return (new Install(manager, repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "scan":
return Scan(GetGameInstance(manager), user);
case "list":
return (new List(repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "show":
return (new Show(repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "replace":
Scan(GetGameInstance(manager), user, cmdline.action);
return (new Replace(manager, repoData, user)).RunCommand(GetGameInstance(manager), (ReplaceOptions)cmdline.options);
case "upgrade":
Scan(GetGameInstance(manager), user, cmdline.action);
return (new Upgrade(manager, repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "search":
return (new Search(repoData, user)).RunCommand(GetGameInstance(manager), options);
case "remove":
return (new Remove(manager, repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options);
case "import":
return (new Import(manager, repoData, user)).RunCommand(GetGameInstance(manager), options);
case "clean":
return Clean(manager.Cache);
case "compare":
return (new Compare(user)).RunCommand(cmdline.options);
default:
user.RaiseMessage(Properties.Resources.MainUnknownCommand);
return Exit.BADOPT;
}
}
catch (NoGameInstanceKraken)
{
return printMissingInstanceError(user);
}
finally
{
RegistryManager.DisposeAll();
}
}
internal static CkanModule LoadCkanFromFile(string ckan_file)
=> CkanModule.FromFile(ckan_file);
private static int printMissingInstanceError(IUser user)
{
user.RaiseMessage(Properties.Resources.MainMissingInstance);
return Exit.ERROR;
}
#if NETFRAMEWORK || WINDOWS
#if NET5_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
private static int Gui(GameInstanceManager manager, GuiOptions options, string[] args)
{
// TODO: Sometimes when the GUI exits, we get a System.ArgumentException,
// but trying to catch it here doesn't seem to help. Dunno why.
// GUI expects its first param to be an identifier, don't confuse it
GUI.GUI.Main_(args.Except(new string[] {"--verbose", "--debug", "--show-console", "--asroot"})
.ToArray(),
options.NetUserAgent, manager, options.ShowConsole);
return Exit.OK;
}
#endif
private static int ConsoleUi(GameInstanceManager manager, ConsoleUIOptions opts)
{
// Debug/verbose output just messes up the screen
LogManager.GetRepository().Threshold = Level.Warn;
return ConsoleUI.ConsoleUI.Main_(manager,
opts.Theme ?? Environment.GetEnvironmentVariable("CKAN_CONSOLEUI_THEME") ?? "default",
opts.NetUserAgent, opts.Debug);
}
private static int Version(IUser user)
{
user.RaiseMessage("{0}", Meta.GetVersion(VersionFormat.Full));
return Exit.OK;
}
/// <summary>
/// Scans the game instance. Detects installed mods to mark as auto-detected and checks the consistency
/// </summary>
/// <param name="inst">The instance to scan</param>
/// <param name="user"></param>
/// <param name="next_command">Changes the output message if set.</param>
/// <returns>Exit.OK if instance is consistent, Exit.ERROR otherwise </returns>
private static int Scan(CKAN.GameInstance inst,
IUser user,
string? next_command = null)
{
try
{
var repoData = ServiceLocator.Container.Resolve<RepositoryDataManager>();
RegistryManager.Instance(inst, repoData).ScanUnmanagedFiles();
return Exit.OK;
}
catch (InconsistentKraken kraken)
{
if (next_command == null)
{
user.RaiseError("{0}", kraken.Message);
user.RaiseError(Properties.Resources.ScanNotSaved);
}
else
{
user.RaiseMessage(Properties.Resources.ScanPreliminaryInconsistent, next_command);
}
return Exit.ERROR;
}
}
private static int Clean(NetModuleCache? cache)
{
cache?.RemoveAll();
return Exit.OK;
}
}
public class NoGameInstanceKraken : Kraken
{
public NoGameInstanceKraken() { }
}
}