forked from LavishSoftware/ISBoxerEVELauncher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Settings.cs
483 lines (423 loc) · 18.3 KB
/
Settings.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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace ISBoxerEVELauncher
{
/// <summary>
/// Any data we want to store, and a little that we don't.
/// </summary>
public class Settings : IDisposable, INotifyPropertyChanged
{
public Settings()
{
Accounts = new ObservableCollection<EVEAccount>();
EULAAccepted = DateTime.MinValue;
MasterKeyRequested = DateTime.MinValue;
EVESharedCachePath = App.DetectedEVESharedCachePath;
_LaunchDelay = 2;
}
/// <summary>
/// List of EVE Accounts we've presumably verified access to. (A user may have manually edited the Settings file.)
/// </summary>
public ObservableCollection<EVEAccount> Accounts { get; set; }
InnerSpaceGameProfile _TranquilityGameProfile;
/// <summary>
/// An Inner Space Game Profile to use for launching Tranquility clients
/// </summary>
public InnerSpaceGameProfile TranquilityGameProfile { get { return _TranquilityGameProfile; } set { _TranquilityGameProfile = value; OnPropertyChanged("TranquilityGameProfile"); } }
InnerSpaceGameProfile _SingularityGameProfile;
/// <summary>
/// An Inner Space Game Profile to use for launching Singularity clients
/// </summary>
public InnerSpaceGameProfile SingularityGameProfile { get { return _SingularityGameProfile; } set { _SingularityGameProfile = value; OnPropertyChanged("SingularityGameProfile"); } }
bool _UseSingularity;
/// <summary>
/// If Singularity is to be used for in-app Launch button clicks
/// </summary>
public bool UseSingularity { get { return _UseSingularity; } set { _UseSingularity = value; OnPropertyChanged("UseSingularity"); } }
float _LaunchDelay;
/// <summary>
/// Delay between game launches, in seconds
/// </summary>
public float LaunchDelay { get { return _LaunchDelay; } set { _LaunchDelay = value; OnPropertyChanged("LaunchDelay"); } }
DirectXVersion _UseDirectXVersion;
/// <summary>
/// DirectX version to be used for in-app Launch button clicks
/// </summary>
public DirectXVersion UseDirectXVersion { get { return _UseDirectXVersion; } set { _UseDirectXVersion = value; OnPropertyChanged("UseDirectXVersion"); } }
string _EVESharedCachePath;
/// <summary>
/// Path to SharedCache
/// </summary>
public string EVESharedCachePath { get { return _EVESharedCachePath; } set { _EVESharedCachePath = value; OnPropertyChanged("EVESharedCachePath"); } }
public string GetTranquilityPath()
{
if (string.IsNullOrEmpty(EVESharedCachePath))
return null;
return Path.Combine(EVESharedCachePath, "tq");
}
public string GetTranquilityEXE()
{
if (string.IsNullOrEmpty(EVESharedCachePath))
return null;
return Path.Combine(GetTranquilityPath(), "bin\\exefile.exe");
}
public string GetSingularityPath()
{
if (string.IsNullOrEmpty(EVESharedCachePath))
return null;
return Path.Combine(EVESharedCachePath, "sisi");
}
public string GetSingularityEXE()
{
if (string.IsNullOrEmpty(EVESharedCachePath))
return null;
return Path.Combine(GetSingularityPath() , "bin\\exefile.exe");
}
string _MasterKeyCheck;
/// <summary>
/// A SHA256 hash encoded as Base64, used to check whether the user entered the correct Master Password
/// </summary>
public string MasterKeyCheck { get { return _MasterKeyCheck; } set { _MasterKeyCheck = value; OnPropertyChanged("MasterKeyCheck"); OnPropertyChanged("UseMasterKey"); } }
string _MasterKeyCheckIV;
/// <summary>
/// A Base64 Initialization Vector used to ensure the MasterKeyCheck is not the same twice for the same Master Password
/// </summary>
public string MasterKeyCheckIV { get { return _MasterKeyCheckIV; } set { _MasterKeyCheckIV = value; OnPropertyChanged("MasterKeyCheckIV"); } }
DateTime _EULAAccepted;
/// <summary>
/// We'll show the EULA because CCP will prefer it and we're all friends here. But only when it has actually changed since our user has been shown the EULA, if we can help it...
/// </summary>
public DateTime EULAAccepted { get { return _EULAAccepted; } set { _EULAAccepted = value; OnPropertyChanged("EULAAccepted"); } }
/// <summary>
/// Has a Master Key been configured?
/// </summary>
[XmlIgnore]
public bool UseMasterKey
{
get
{
return !string.IsNullOrEmpty(MasterKeyCheck);
}
}
/// <summary>
/// Have we *entered* the Password Master Key since launching this app? ...
/// </summary>
/// <returns></returns>
[XmlIgnore]
public bool HasPasswordMasterKey
{
get
{
return PasswordMasterKey != null && PasswordMasterKey.HasData;
}
}
SecureStringWrapper _PasswordMasterKey;
/// <summary>
/// The already-encrypted Master Password. The Password itself is discarded and wiped.
/// </summary>
[XmlIgnore]
public SecureStringWrapper PasswordMasterKey
{
get
{
return _PasswordMasterKey;
}
set
{
_PasswordMasterKey = value;
OnPropertyChanged("PasswordMasterKey");
OnPropertyChanged("HasPasswordMasterKey");
}
}
DateTime _MasterKeyRequested;
/// <summary>
/// The time Master Key was requested from the master ISBoxer EVE Launcher instance
/// </summary>
[XmlIgnore]
public DateTime MasterKeyRequested { get { return _MasterKeyRequested; } set { _MasterKeyRequested = value; OnPropertyChanged("MasterKeyRequested"); } }
/// <summary>
/// This is used to generate the Master Key Check, along with an IV and the Master Key
/// </summary>
const string MasterKeyCheckPlaintext = "ISBoxerEVELauncher";
/// <summary>
/// This is used as Salt for building a Master Key from the Master Password, intending to compensate for YOUR shitty Master Password. That's right, it's YOUR fault.
/// </summary>
const string MasterKeySaltString = "Salt and pepper the steak before grilling";
/// <summary>
/// The Salt String stored as a Unicode Byte array, since that's what we'll be using later.
/// </summary>
static byte[] MasterKeySalt = Encoding.Unicode.GetBytes(MasterKeySaltString);
/// <summary>
/// If a Master Key has been configured, but not entered this session, request it from the user
/// </summary>
public bool RequestMasterPassword()
{
if (UseMasterKey && (PasswordMasterKey==null || !PasswordMasterKey.HasData))
{
Windows.MasterKeyEntryWindow mkew = new Windows.MasterKeyEntryWindow();
mkew.ShowDialog();
}
return HasPasswordMasterKey;
}
/// <summary>
/// Using the Password Master Key, generate a hash that can be tested to check if we enter the correct Master Password
/// </summary>
void GenerateMasterKeyCheck()
{
using (RijndaelManaged rjmIVGenerator = new RijndaelManaged())
{
rjmIVGenerator.GenerateIV();
MasterKeyCheckIV = Convert.ToBase64String(rjmIVGenerator.IV);
using (SecureBytesWrapper sbwPreHash = new SecureBytesWrapper())
{
byte[] plaintextBytes = Encoding.Unicode.GetBytes(MasterKeyCheckPlaintext);
using (SecureBytesWrapper sbwKey = new SecureBytesWrapper(App.Settings.PasswordMasterKey, true))
{
sbwPreHash.Bytes = new byte[rjmIVGenerator.IV.Length + plaintextBytes.Length + sbwKey.Bytes.Length];
System.Buffer.BlockCopy(rjmIVGenerator.IV, 0, sbwPreHash.Bytes, 0, rjmIVGenerator.IV.Length);
System.Buffer.BlockCopy(plaintextBytes, 0, sbwPreHash.Bytes, rjmIVGenerator.IV.Length, plaintextBytes.Length);
System.Buffer.BlockCopy(sbwKey.Bytes, 0, sbwPreHash.Bytes, rjmIVGenerator.IV.Length + plaintextBytes.Length, sbwKey.Bytes.Length);
}
using (SHA256Managed sha = new SHA256Managed())
{
// convert to Base64 and this is our check
MasterKeyCheck = Convert.ToBase64String(sha.ComputeHash(sbwPreHash.Bytes));
}
}
}
}
/// <summary>
/// Determine if the user has entered the correct Master Password, by testing with MasterKeyCheck and friends. If it is, keep the Master Key.
/// </summary>
/// <param name="masterPassword"></param>
/// <returns></returns>
public bool TryMasterPassword(System.Security.SecureString masterPassword)
{
if (masterPassword == null || masterPassword.Length < 1)
return false;
if (string.IsNullOrEmpty(MasterKeyCheck) || string.IsNullOrEmpty(MasterKeyCheckIV))
return false;
using (SecureBytesWrapper sbwKey = new SecureBytesWrapper())
{
// first we need to create a key out of the password.
using (SHA256Managed sha = new SHA256Managed())
{
using (SecureStringWrapper ssw = new SecureStringWrapper(masterPassword))
{
byte[] passwordBytes = ssw.ToByteArray();
using (SecureBytesWrapper sbw = new SecureBytesWrapper())
{
sbw.Bytes = new byte[MasterKeySalt.Length + passwordBytes.Length];
System.Buffer.BlockCopy(MasterKeySalt, 0, sbw.Bytes, 0, MasterKeySalt.Length);
System.Buffer.BlockCopy(passwordBytes, 0, sbw.Bytes, MasterKeySalt.Length, passwordBytes.Length);
sbwKey.Bytes = sha.ComputeHash(sbw.Bytes);
}
}
}
return TryPasswordMasterKey(sbwKey.Bytes);
}
}
public void SetPasswordMasterKeyBytes(byte[] bytes)
{
PasswordMasterKey = SecureStringWrapper.ConvertToHex(bytes);
}
/// <summary>
/// Using the provided Master Password, set the *new* Master Key. Populates Master Key Check, and encrypts+stores already-entered EVE Account passwords
/// </summary>
/// <param name="masterPassword"></param>
public void SetPasswordMasterKey(System.Security.SecureString masterPassword)
{
ClearPasswordMasterKey();
if (masterPassword == null || masterPassword.Length < 1)
{
return;
}
using (SHA256Managed sha = new SHA256Managed())
{
using (SecureStringWrapper ssw = new SecureStringWrapper(masterPassword))
{
byte[] passwordBytes = ssw.ToByteArray();
using (SecureBytesWrapper sbw = new SecureBytesWrapper())
{
sbw.Bytes = new byte[MasterKeySalt.Length + passwordBytes.Length];
System.Buffer.BlockCopy(MasterKeySalt, 0, sbw.Bytes, 0, MasterKeySalt.Length);
System.Buffer.BlockCopy(passwordBytes, 0, sbw.Bytes, MasterKeySalt.Length, passwordBytes.Length);
sbw.Bytes = sha.ComputeHash(sbw.Bytes);
SetPasswordMasterKeyBytes(sbw.Bytes);
}
}
}
GenerateMasterKeyCheck();
foreach (EVEAccount account in Accounts)
{
account.EncryptPassword();
account.EncryptCharacterName();
}
Store();
}
/// <summary>
/// Determine if this is the correct Master Key, by testing with MasterKeyCheck and friends. If it is, keep the Master Key.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public bool TryPasswordMasterKey(byte[] bytes)
{
if (string.IsNullOrEmpty(MasterKeyCheck) || string.IsNullOrEmpty(MasterKeyCheckIV))
return false;
byte[] masterKeyCheckIV = Convert.FromBase64String(MasterKeyCheckIV);
using (SecureBytesWrapper sbwPreHash = new SecureBytesWrapper())
{
byte[] plaintextBytes = Encoding.Unicode.GetBytes(MasterKeyCheckPlaintext);
sbwPreHash.Bytes = new byte[masterKeyCheckIV.Length + plaintextBytes.Length + bytes.Length];
System.Buffer.BlockCopy(masterKeyCheckIV, 0, sbwPreHash.Bytes, 0, masterKeyCheckIV.Length);
System.Buffer.BlockCopy(plaintextBytes, 0, sbwPreHash.Bytes, masterKeyCheckIV.Length, plaintextBytes.Length);
System.Buffer.BlockCopy(bytes, 0, sbwPreHash.Bytes, masterKeyCheckIV.Length + plaintextBytes.Length, bytes.Length);
using (SHA256Managed sha = new SHA256Managed())
{
// convert to Base64 and this is our check
if (!MasterKeyCheck.Equals(Convert.ToBase64String(sha.ComputeHash(sbwPreHash.Bytes))))
{
return false;
}
}
}
SetPasswordMasterKeyBytes(bytes);
return true;
}
/// <summary>
/// Clear out our Password Master Key (if we've even entered it...), and remove all encrypted+stored passwords
/// </summary>
public void ClearPasswordMasterKey()
{
foreach (EVEAccount account in Accounts)
{
account.ClearEncryptedPassword();
account.ClearEncryptedCharacterName();
}
MasterKeyCheck = null;
MasterKeyCheckIV = null;
if (PasswordMasterKey!=null)
{
PasswordMasterKey.Dispose();
}
PasswordMasterKey = null;
Store();
}
/// <summary>
/// Load XML Settings from the DefaultFilename
/// </summary>
/// <returns></returns>
public static Settings Load()
{
return Load(DefaultFilename);
}
/// <summary>
/// Load XML Settings! (I am good at comments)
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public static Settings Load(string filename)
{
try
{
XmlSerializer s = new XmlSerializer(typeof(Settings));
using (TextReader r = new StreamReader(filename, System.Text.Encoding.UTF8))
{
Settings settings = (Settings)s.Deserialize(r);
settings.SingularityGameProfile = App.FindGlobalGameProfile(settings.SingularityGameProfile);
settings.TranquilityGameProfile = App.FindGlobalGameProfile(settings.TranquilityGameProfile);
return settings;
}
}
catch (System.IO.FileNotFoundException e)
{
throw;
}
catch (Exception e)
{
// MessageBox.Show("Error loading file " + filename + "... " + Environment.NewLine + e.ToString());
// return null;
throw;
}
}
/// <summary>
/// Where we find the Settings file.
/// </summary>
public static string DefaultFilename
{
get
{
return App.ISBoxerEVELauncherPath + @"\ISBoxerEVELauncher.Settings.XML";
}
}
/// <summary>
/// Stores in XML to the DefaultFilename
/// </summary>
public void Store()
{
Store(DefaultFilename);
}
bool cannotSave = false;
/// <summary>
/// Stores Settings in XML!
/// </summary>
/// <param name="filename"></param>
public void Store(string filename)
{
try
{
using (TextWriter w = new StreamWriter(filename, false, System.Text.Encoding.UTF8))
{
XmlSerializer s = new XmlSerializer(typeof(Settings));
s.Serialize(w, this);
}
}
catch(UnauthorizedAccessException uae)
{
if (cannotSave)
return;
cannotSave = true;
System.Windows.MessageBox.Show("ISBoxer EVE Launcher cannot save its Settings to "+DefaultFilename+". You may need to Run as Administrator!");
return;
}
catch (Exception e)
{
throw;
}
}
/// <summary>
/// Get rid of the evidence
/// </summary>
public void Dispose()
{
if (PasswordMasterKey != null)
{
PasswordMasterKey.Dispose();
PasswordMasterKey = null;
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string value)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(value));
}
}
public void OnPropertyChanged(string value)
{
FirePropertyChanged(value);
}
#endregion
}
}