\s*
\s*(.*)\s*
\s*
(.*)
", RegexOptions.Compiled);
15 |
16 | public static async Task
GetChangeLogAsync(long workshopID)
17 | {
18 | if (Cache.ContainsKey(workshopID))
19 | return Cache[workshopID];
20 |
21 | try
22 | {
23 | using (var client = new WebClient())
24 | {
25 | var htmlstrip = new Regex("<.*?>", RegexOptions.Compiled);
26 | var changelogRaw = await client.DownloadStringTaskAsync("https://steamcommunity.com/sharedfiles/filedetails/changelog/" + workshopID);
27 |
28 | var output = new StringBuilder();
29 | foreach (Match m in Regexp.Matches(changelogRaw))
30 | {
31 | var desc = m.Groups[2].Value.Replace("
", "\r\n\t");
32 | desc = WebUtility.HtmlDecode(desc);
33 | desc = htmlstrip.Replace(desc, "").Trim();
34 |
35 | if (desc.Length == 0)
36 | desc = "No description.";
37 |
38 | output.AppendLine(m.Groups[1].Value.Trim());
39 | output.AppendLine("\t" + desc);
40 | output.AppendLine();
41 | }
42 |
43 | Cache.Add(workshopID, output.ToString());
44 |
45 | return output.ToString();
46 | }
47 | }
48 | catch (WebException)
49 | {
50 | return "An error occurred while loading the changelog.";
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Serialization/ModListConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using XCOM2Launcher.Mod;
6 | using XCOM2Launcher.XCOM;
7 |
8 | namespace XCOM2Launcher.Serialization
9 | {
10 | internal class ModListConverter : JsonConverter
11 | {
12 | public override bool CanRead => true;
13 |
14 | public override bool CanConvert(Type objectType) => objectType == typeof (Settings);
15 |
16 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
17 | {
18 | // Load JObject from stream
19 | var jObject = JObject.Load(reader);
20 |
21 | if (jObject["Arguments"].Type == JTokenType.Object)
22 | {
23 | // Transform Arguments object to string
24 | var args = jObject["Arguments"].ToObject();
25 | jObject["Arguments"] = new JValue(args.ToString());
26 | }
27 |
28 | var settings = jObject.ToObject();
29 |
30 | // repair old formats
31 | var modToken = jObject["Mods"];
32 | if (modToken == null)
33 | return settings;
34 |
35 | if (modToken["Entries"] == null)
36 | return settings;
37 |
38 | try
39 | {
40 | var i = 0;
41 | var mods = modToken.ToObject>>();
42 | if (mods.Count > 0)
43 | {
44 | foreach (var entry in mods)
45 | foreach (var m in entry.Value)
46 | {
47 | m.Index = i++;
48 | settings.Mods.AddMod(entry.Key, m);
49 | }
50 | }
51 | }
52 | catch
53 | {
54 | // do nothing
55 | }
56 |
57 |
58 | return settings;
59 | }
60 |
61 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => JToken.FromObject(value).WriteTo(writer);
62 | }
63 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xcom2-launcher", "xcom2-launcher\xcom2-launcher.csproj", "{FC343C56-9BC8-483A-A254-3484B2137E24}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | OSX-Linux|Any CPU = OSX-Linux|Any CPU
13 | OSX-Linux|x64 = OSX-Linux|x64
14 | Release|Any CPU = Release|Any CPU
15 | Release|x64 = Release|x64
16 | Windows|Any CPU = Windows|Any CPU
17 | Windows|x64 = Windows|x64
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Debug|x64.ActiveCfg = Debug|x64
23 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Debug|x64.Build.0 = Debug|x64
24 | {FC343C56-9BC8-483A-A254-3484B2137E24}.OSX-Linux|Any CPU.ActiveCfg = Release|Any CPU
25 | {FC343C56-9BC8-483A-A254-3484B2137E24}.OSX-Linux|Any CPU.Build.0 = Release|Any CPU
26 | {FC343C56-9BC8-483A-A254-3484B2137E24}.OSX-Linux|x64.ActiveCfg = Release|x64
27 | {FC343C56-9BC8-483A-A254-3484B2137E24}.OSX-Linux|x64.Build.0 = Release|x64
28 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Release|x64.ActiveCfg = Release|x64
31 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Release|x64.Build.0 = Release|x64
32 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Windows|Any CPU.ActiveCfg = Release|Any CPU
33 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Windows|Any CPU.Build.0 = Release|Any CPU
34 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Windows|x64.ActiveCfg = Release|x64
35 | {FC343C56-9BC8-483A-A254-3484B2137E24}.Windows|x64.Build.0 = Release|x64
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Steam/ItemDetailsRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using Steamworks;
3 | using XCOM2Launcher.Classes.Steam;
4 |
5 | namespace XCOM2Launcher.Steam
6 | {
7 | public class ItemDetailsRequest
8 | {
9 | private CallResult _onQueryCompleted;
10 |
11 | private UGCQueryHandle_t _queryHandle;
12 |
13 | public ItemDetailsRequest(ulong id)
14 | {
15 | ID = id;
16 | }
17 |
18 |
19 | public ulong ID { get; }
20 |
21 | public bool Success { get; private set; }
22 | public bool Finished { get; private set; }
23 | public bool Cancelled { get; private set; }
24 |
25 |
26 | public SteamUGCDetails_t Result { get; private set; }
27 |
28 | public ItemDetailsRequest Send()
29 | {
30 | SteamAPIWrapper.Init();
31 |
32 | _onQueryCompleted = CallResult.Create(QueryCompleted);
33 | _queryHandle = SteamUGC.CreateQueryUGCDetailsRequest(new[] {ID.ToPublishedFileID()}, 1);
34 |
35 | var apiCall = SteamUGC.SendQueryUGCRequest(_queryHandle);
36 | _onQueryCompleted.Set(apiCall);
37 |
38 | return this;
39 | }
40 |
41 | public bool Cancel()
42 | {
43 | Cancelled = true;
44 | return !Finished;
45 | }
46 |
47 | public ItemDetailsRequest WaitForResult()
48 | {
49 | // Wait for Response
50 | while (!Finished && !Cancelled)
51 | {
52 | Thread.Sleep(10);
53 | SteamAPIWrapper.RunCallbacks();
54 | }
55 |
56 | if (Cancelled)
57 | return this;
58 |
59 | // Retrieve Value
60 | SteamUGCDetails_t result;
61 | Success = SteamUGC.GetQueryUGCResult(_queryHandle, 0, out result);
62 |
63 | Result = result;
64 | return this;
65 | }
66 |
67 | public string GetPreviewURL()
68 | {
69 | if (!Finished)
70 | return null;
71 |
72 | string url;
73 |
74 | SteamUGC.GetQueryUGCPreviewURL(_queryHandle, 0, out url, 1000);
75 | return url;
76 | }
77 |
78 | private void QueryCompleted(SteamUGCQueryCompleted_t pCallback, bool bIOFailure)
79 | {
80 | Finished = true;
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Settings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Newtonsoft.Json;
6 | using XCOM2Launcher.Mod;
7 | using XCOM2Launcher.Serialization;
8 | using XCOM2Launcher.XCOM;
9 |
10 | namespace XCOM2Launcher
11 | {
12 | public class Settings
13 | {
14 | private string _gamePath;
15 |
16 | public string GamePath
17 | {
18 | get { return _gamePath; }
19 | set
20 | {
21 | _gamePath = value;
22 | XCOM2.GameDir = value;
23 | }
24 | }
25 |
26 | public string SavePath { get; set; }
27 |
28 | public List ModPaths { get; set; } = new List();
29 |
30 | public string Arguments { get; set; } = "-Review -NoRedscreens";
31 |
32 | public bool CheckForUpdates { get; set; } = true;
33 |
34 | public bool ShowHiddenElements { get; set; } = false;
35 |
36 | public bool CloseAfterLaunch { get; set; } = false;
37 |
38 | public ModList Mods { get; set; } = new ModList();
39 |
40 | public Dictionary Windows { get; set; } = new Dictionary();
41 |
42 | internal void ImportMods()
43 | {
44 | foreach (var dir in ModPaths)
45 | Mods.ImportMods(dir);
46 |
47 | Mods.MarkDuplicates();
48 | }
49 |
50 | public string GetWorkshopPath()
51 | {
52 | return ModPaths.FirstOrDefault(modPath => modPath.IndexOf("steamapps\\workshop\\content\\268500\\", StringComparison.OrdinalIgnoreCase) != -1);
53 | }
54 |
55 | #region Serialization
56 |
57 | public static Settings FromFile(string file)
58 | {
59 | if (!File.Exists(file))
60 | throw new FileNotFoundException(file);
61 |
62 |
63 | using (var stream = File.OpenRead(file))
64 | using (var reader = new StreamReader(stream))
65 | {
66 | var serializer = new JsonSerializer();
67 | serializer.Converters.Add(new ModListConverter());
68 |
69 | return (Settings) serializer.Deserialize(reader, typeof (Settings));
70 | }
71 | }
72 |
73 | public void SaveFile(string file)
74 | {
75 | var settings = new JsonSerializerSettings
76 | {
77 | DefaultValueHandling = DefaultValueHandling.Ignore,
78 | Converters = new List {new ModListConverter()}
79 | };
80 |
81 | File.WriteAllText(file, JsonConvert.SerializeObject(this, Formatting.Indented, settings));
82 | }
83 |
84 | #endregion
85 | }
86 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/XCOM/Arguments.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Drawing.Design;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using XCOM2Launcher.PropertyGrid;
9 |
10 | namespace XCOM2Launcher.XCOM
11 | {
12 | public class Arguments
13 | {
14 | [DisplayName("-review")]
15 | [Description("Final main menu? \r\nDefault: true")]
16 | [DefaultValue(true)]
17 | public bool review { get; set; } = true;
18 |
19 | [DisplayName("-noredscreens")]
20 | [Description("Hide error popups?\r\nDefault: true")]
21 | [DefaultValue(true)]
22 | public bool noRedScreens { get; set; } = true;
23 |
24 | [DisplayName("-log")]
25 | [Description("Show log console?\r\nDefault: false")]
26 | [DefaultValue(false)]
27 | public bool log { get; set; } = false;
28 |
29 | [DisplayName("-crashdumpwatcher")]
30 | [Description("No idea.\r\nDefault: true")]
31 | [DefaultValue(true)]
32 | public bool crashDumpWatcher { get; set; } = true;
33 |
34 | [DisplayName("-nostartupmovies")]
35 | [Description("Skip intro movies?\r\nDefault: false")]
36 | [DefaultValue(false)]
37 | public bool noStartupMovies { get; set; } = false;
38 |
39 | [DisplayName("-language")]
40 | [Description("Force language\r\nDefault: empty")]
41 | [DefaultValue("")]
42 | public string Language { get; set; } = "";
43 |
44 |
45 | [DisplayName("-allowconsole")]
46 | [Description("Allow cheat console?\r\nDefault: false")]
47 | [DefaultValue(false)]
48 | public bool allowConsole { get; set; } = false;
49 |
50 | [DisplayName("-autodebug")]
51 | [Description("No idea.\r\nDefault: false")]
52 | [DefaultValue(false)]
53 | public bool autoDebug { get; set; } = false;
54 |
55 | [DisplayName("Additional arguments")]
56 | [Description("Add something extra")]
57 | [DefaultValue(null)]
58 | public string Custom { get; set; } = null;
59 |
60 | public override string ToString()
61 | {
62 | string args = "-fromLauncher";
63 |
64 | if (log)
65 | args += " -log";
66 |
67 | if (review)
68 | args += " -review";
69 |
70 | if (Language.Length > 0)
71 | args += " -language=" + Language;
72 |
73 | if (noRedScreens)
74 | args += " -noRedScreens";
75 |
76 | if (noStartupMovies)
77 | args += " -noStartupMovies";
78 |
79 | if (crashDumpWatcher)
80 | args += " -CrashDumpWatcher";
81 |
82 | if (allowConsole)
83 | args += " -allowConsole";
84 |
85 | if (autoDebug)
86 | args += " -autoDebug";
87 |
88 |
89 | if (!string.IsNullOrEmpty(Custom))
90 | args += " " + Custom;
91 |
92 |
93 | return args;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Dieser Code wurde von einem Tool generiert.
4 | // Laufzeitversion:4.0.30319.42000
5 | //
6 | // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
7 | // der Code erneut generiert wird.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace XCOM2Launcher.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
17 | ///
18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
19 | // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
21 | // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XCOM2Launcher.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
51 | /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Icon ähnlich wie (Symbol).
65 | ///
66 | internal static System.Drawing.Icon xcom {
67 | get {
68 | object obj = ResourceManager.GetObject("xcom", resourceCulture);
69 | return ((System.Drawing.Icon)(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/CleanModsForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 | using XCOM2Launcher.Mod;
7 |
8 | namespace XCOM2Launcher.Forms
9 | {
10 | public partial class CleanModsForm : Form
11 | {
12 | public CleanModsForm(Settings settings)
13 | {
14 | InitializeComponent();
15 |
16 | //
17 | Mods = settings.Mods;
18 |
19 | // todo save cleaning settings?
20 | // Register Events
21 | button1.Click += onStartButtonClicked;
22 | }
23 |
24 | private ModList Mods { get; }
25 |
26 | private void onStartButtonClicked(object sender, EventArgs e)
27 | {
28 | var result = MessageBox.Show("Are you sure?\r\nThis might cause problems and can not be undone.", "Confirm", MessageBoxButtons.OKCancel);
29 | if (result != DialogResult.OK)
30 | return;
31 |
32 | var source_mode = source_groupbox.Controls.OfType().FirstOrDefault(r => r.Checked).Name;
33 | var shader_mode = shader_groupbox.Controls.OfType().FirstOrDefault(r => r.Checked).Name;
34 |
35 | foreach (var m in Mods.All)
36 | {
37 | // Source Files
38 | if (hasSourceFiles(m))
39 | {
40 | if (source_mode == "src_all_radiobutton")
41 | deleteSourceFiles(m);
42 |
43 | else if (source_mode == "src_xcomgame_radiobutton")
44 | if (hasXComGameSourceFiles(m))
45 | deleteXComGameSourceFiles(m);
46 | }
47 |
48 | // Shader Cache
49 | if (hasModShaderCache(m))
50 | {
51 | if (shader_mode == "shadercache_all_radiobutton")
52 | deleteModShaderCache(m);
53 |
54 | else if (shader_mode == "shadercache_empty_radiobutton")
55 | if (hasEmptyModShaderCache(m))
56 | deleteModShaderCache(m);
57 | }
58 | }
59 |
60 | Close();
61 | }
62 |
63 |
64 | internal static bool hasModShaderCache(ModEntry m)
65 | {
66 | return File.Exists(Path.Combine(m.Path, "Content", m.ID + "_ModShaderCache.upk"));
67 | }
68 |
69 | internal static bool hasEmptyModShaderCache(ModEntry m)
70 | {
71 | var file = Path.Combine(m.Path, "Content", m.ID + "_ModShaderCache.upk");
72 | return File.Exists(file) && new FileInfo(file).Length == 371;
73 | }
74 |
75 | internal static void deleteModShaderCache(ModEntry m)
76 | {
77 | File.Delete(Path.Combine(m.Path, "Content", m.ID + "_ModShaderCache.upk"));
78 | }
79 |
80 |
81 | internal static bool hasSourceFiles(ModEntry m)
82 | {
83 | return Directory.Exists(Path.Combine(m.Path, "src"));
84 | }
85 |
86 | internal static bool hasXComGameSourceFiles(ModEntry m)
87 | {
88 | return Directory.Exists(Path.Combine(m.Path, "src", "XComGame"));
89 | }
90 |
91 | internal static void deleteSourceFiles(ModEntry m)
92 | {
93 | Directory.Delete(Path.Combine(m.Path, "src"), true);
94 | }
95 |
96 | internal static void deleteXComGameSourceFiles(ModEntry m)
97 | {
98 | Directory.Delete(Path.Combine(m.Path, "src", "XComGame"), true);
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/XCOM/old/Config.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Windows.Forms;
5 |
6 | namespace XCOM2Launcher.XCOM.Config
7 | {
8 | [TypeConverter(typeof(ConfigSettingsObjectConverter))]
9 | public class ConfigFile
10 | {
11 | public string Name { get; set; }
12 |
13 | [Browsable(false)]
14 | public List Settings { get; set; }
15 |
16 |
17 | public ConfigFile()
18 | {
19 | Settings = new List();
20 | }
21 |
22 | }
23 |
24 | internal class ConfigSettingsObjectConverter : ExpandableObjectConverter
25 | {
26 | public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
27 | {
28 | // Config Name
29 | ConfigFile obj = value as ConfigFile;
30 |
31 | List customProps = obj.Settings;
32 | PropertyDescriptorCollection stdProps = base.GetProperties(context, value, attributes);
33 |
34 | PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + obj.Settings.Count];
35 | stdProps.CopyTo(props, 0);
36 |
37 | int index = stdProps.Count;
38 | foreach (ConfigSetting prop in customProps)
39 | {
40 | props[index++] = new ConfigFilePropertyDescriptor(prop);
41 | }
42 |
43 | return new PropertyDescriptorCollection(props);
44 | }
45 | }
46 |
47 |
48 | internal class ConfigFilePropertyDescriptor : PropertyDescriptor
49 | {
50 | public ConfigSetting prop { get; set; }
51 | public ConfigFilePropertyDescriptor(ConfigSetting prop)
52 | : base(prop.Name, null)
53 | {
54 | this.prop = prop;
55 | }
56 |
57 | public override string Category
58 | {
59 | get { return prop.Category; }
60 | }
61 |
62 | public override string Description
63 | {
64 | get { return prop.Name; }
65 | }
66 |
67 | public override string Name
68 | {
69 | get { return prop.Name; }
70 | }
71 |
72 | public override object GetValue(object component)
73 | {
74 | return (component as ConfigFile).Settings.Find(s => s.Name == prop.Name).Value;
75 | }
76 |
77 | public override void SetValue(object component, object value)
78 | {
79 | (component as ConfigFile).Settings.Find(s => s.Name == prop.Name).Value = value;
80 | }
81 |
82 | public override bool ShouldSerializeValue(object component) { return false; }// ((ConfigFile)component).Settings[prop.Name] != null; }
83 |
84 | public override void ResetValue(object component) { }
85 | public override bool IsReadOnly { get { return false; } }
86 | public override Type PropertyType { get { return prop.Type; } }
87 | public override bool CanResetValue(object component) { return true; }
88 | public override Type ComponentType
89 | {
90 | get { return typeof(ConfigFile); }
91 | }
92 | }
93 |
94 | public class ConfigSetting
95 | {
96 | public string Name { get; set; }
97 | public string Desc { get; set; }
98 | public object Value { get; set; }
99 | public object DefaultValue { get; set; }
100 | public string Category { get; set; }
101 |
102 | public Type Type
103 | {
104 | get { return Value.GetType(); }
105 | // set { Value = null; }// Activator.CreateInstance(value); }
106 | }
107 |
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Steam/Workshop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using Steamworks;
5 | using XCOM2Launcher.Classes.Steam;
6 |
7 | namespace XCOM2Launcher.Steam
8 | {
9 | public static class Workshop
10 | {
11 |
12 | public static ulong[] GetSubscribedItems()
13 | {
14 | SteamAPIWrapper.Init();
15 |
16 | var num = SteamUGC.GetNumSubscribedItems();
17 | var ids = new PublishedFileId_t[num];
18 | SteamUGC.GetSubscribedItems(ids, num);
19 |
20 | return ids.Select(t => t.m_PublishedFileId).ToArray();
21 | }
22 |
23 | public static void Unsubscribe(ulong id)
24 | {
25 | SteamAPIWrapper.Init();
26 |
27 | SteamUGC.UnsubscribeItem(id.ToPublishedFileID());
28 | SteamAPIWrapper.RunCallbacks();
29 | }
30 |
31 | public static SteamUGCDetails_t GetDetails(ulong id)
32 | {
33 | var request = new ItemDetailsRequest(id);
34 |
35 | request.Send().WaitForResult();
36 |
37 | return request.Result;
38 | }
39 |
40 |
41 | public static EItemState GetDownloadStatus(ulong id)
42 | {
43 | SteamAPIWrapper.Init();
44 | return (EItemState)SteamUGC.GetItemState(new PublishedFileId_t(id));
45 | }
46 |
47 | public static InstallInfo GetInstallInfo(ulong id)
48 | {
49 | SteamAPIWrapper.Init();
50 |
51 | ulong punSizeOnDisk;
52 | string pchFolder;
53 | uint punTimeStamp;
54 |
55 | SteamUGC.GetItemInstallInfo(new PublishedFileId_t(id), out punSizeOnDisk, out pchFolder, 256, out punTimeStamp);
56 |
57 | return new InstallInfo
58 | {
59 | ItemID = id,
60 | SizeOnDisk = punSizeOnDisk,
61 | Folder = pchFolder,
62 | TimeStamp = new DateTime(punTimeStamp * 10)
63 | };
64 | }
65 |
66 | public static UpdateInfo GetDownloadInfo(ulong id)
67 | {
68 | SteamAPIWrapper.Init();
69 |
70 | ulong punBytesProcessed;
71 | ulong punBytesTotal;
72 |
73 | SteamUGC.GetItemDownloadInfo(new PublishedFileId_t(id), out punBytesProcessed, out punBytesTotal);
74 |
75 | return new UpdateInfo
76 | {
77 | ItemID = id,
78 | BytesProcessed = punBytesProcessed,
79 | BytesTotal = punBytesTotal
80 | };
81 | }
82 |
83 |
84 | #region Download Item
85 | public class DownloadItemEventArgs : EventArgs
86 | {
87 | public DownloadItemResult_t Result { get; set; }
88 | }
89 |
90 | // ReSharper disable once NotAccessedField.Local
91 | private static Callback _downloadItemCallback;
92 | public delegate void DownloadItemHandler(object sender, DownloadItemEventArgs e);
93 | public static event DownloadItemHandler OnItemDownloaded;
94 | public static void DownloadItem(ulong id)
95 | {
96 | _downloadItemCallback = Callback.Create(ItemDownloaded);
97 | SteamUGC.DownloadItem(new PublishedFileId_t(id), true);
98 | }
99 |
100 | private static void ItemDownloaded(DownloadItemResult_t result)
101 | {
102 | // Make sure someone is listening to event
103 | if (OnItemDownloaded == null) return;
104 |
105 | DownloadItemEventArgs args = new DownloadItemEventArgs { Result = result };
106 | OnItemDownloaded(null, args);
107 | }
108 |
109 | #endregion
110 |
111 | public static string GetUsername(ulong steamID)
112 | {
113 | // todo
114 | return SteamFriends.GetPlayerNickname(new CSteamID(steamID));
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | # DNX
42 | project.lock.json
43 | artifacts/
44 |
45 | *_i.c
46 | *_p.c
47 | *_i.h
48 | *.ilk
49 | *.meta
50 | *.obj
51 | *.pch
52 | *.pdb
53 | *.pgc
54 | *.pgd
55 | *.rsp
56 | *.sbr
57 | *.tlb
58 | *.tli
59 | *.tlh
60 | *.tmp
61 | *.tmp_proj
62 | *.log
63 | *.vspscc
64 | *.vssscc
65 | .builds
66 | *.pidb
67 | *.svclog
68 | *.scc
69 |
70 | # Chutzpah Test files
71 | _Chutzpah*
72 |
73 | # Visual C++ cache files
74 | ipch/
75 | *.aps
76 | *.ncb
77 | *.opensdf
78 | *.sdf
79 | *.cachefile
80 |
81 | # Visual Studio profiler
82 | *.psess
83 | *.vsp
84 | *.vspx
85 |
86 | # TFS 2012 Local Workspace
87 | $tf/
88 |
89 | # Guidance Automation Toolkit
90 | *.gpState
91 |
92 | # ReSharper is a .NET coding add-in
93 | _ReSharper*/
94 | *.[Rr]e[Ss]harper
95 | *.DotSettings.user
96 |
97 | # JustCode is a .NET coding add-in
98 | .JustCode
99 |
100 | # TeamCity is a build add-in
101 | _TeamCity*
102 |
103 | # DotCover is a Code Coverage Tool
104 | *.dotCover
105 |
106 | # NCrunch
107 | _NCrunch_*
108 | .*crunch*.local.xml
109 |
110 | # MightyMoose
111 | *.mm.*
112 | AutoTest.Net/
113 |
114 | # Web workbench (sass)
115 | .sass-cache/
116 |
117 | # Installshield output folder
118 | [Ee]xpress/
119 |
120 | # DocProject is a documentation generator add-in
121 | DocProject/buildhelp/
122 | DocProject/Help/*.HxT
123 | DocProject/Help/*.HxC
124 | DocProject/Help/*.hhc
125 | DocProject/Help/*.hhk
126 | DocProject/Help/*.hhp
127 | DocProject/Help/Html2
128 | DocProject/Help/html
129 |
130 | # Click-Once directory
131 | publish/
132 |
133 | # Publish Web Output
134 | *.[Pp]ublish.xml
135 | *.azurePubxml
136 | ## TODO: Comment the next line if you want to checkin your
137 | ## web deploy settings but do note that will include unencrypted
138 | ## passwords
139 | #*.pubxml
140 |
141 | *.publishproj
142 |
143 | # NuGet Packages
144 | *.nupkg
145 | # The packages folder can be ignored because of Package Restore
146 | **/packages/*
147 | # except build/, which is used as an MSBuild target.
148 | !**/packages/build/
149 | # Uncomment if necessary however generally it will be regenerated when needed
150 | #!**/packages/repositories.config
151 |
152 | # Windows Azure Build Output
153 | csx/
154 | *.build.csdef
155 |
156 | # Windows Store app package directory
157 | AppPackages/
158 |
159 | # Visual Studio cache files
160 | # files ending in .cache can be ignored
161 | *.[Cc]ache
162 | # but keep track of directories ending in .cache
163 | !*.[Cc]ache/
164 |
165 | # Others
166 | ClientBin/
167 | [Ss]tyle[Cc]op.*
168 | ~$*
169 | *~
170 | *.dbmdl
171 | *.dbproj.schemaview
172 | *.pfx
173 | *.publishsettings
174 | node_modules/
175 | orleans.codegen.cs
176 |
177 | # RIA/Silverlight projects
178 | Generated_Code/
179 |
180 | # Backup & report files from converting an old project file
181 | # to a newer Visual Studio version. Backup files are not needed,
182 | # because we have git ;-)
183 | _UpgradeReport_Files/
184 | Backup*/
185 | UpgradeLog*.XML
186 | UpgradeLog*.htm
187 |
188 | # SQL Server files
189 | *.mdf
190 | *.ldf
191 |
192 | # Business Intelligence projects
193 | *.rdl.data
194 | *.bim.layout
195 | *.bim_*.settings
196 |
197 | # Microsoft Fakes
198 | FakesAssemblies/
199 |
200 | # Node.js Tools for Visual Studio
201 | .ntvs_analysis.dat
202 |
203 | # Visual Studio 6 build log
204 | *.plg
205 |
206 | # Visual Studio 6 workspace options file
207 | *.opt
208 |
209 | # LightSwitch generated files
210 | GeneratedArtifacts/
211 | _Pvt_Extensions/
212 | ModelManifest.xml
213 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/XCOM/ConfigFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 |
6 | namespace XCOM2Launcher.XCOM
7 | {
8 | public class ConfigFile : IniFile
9 | {
10 | public ConfigFile(string filename, bool load = true) : base($"{XCOM2.UserConfigDir}/XCom{filename}.ini", false)
11 | {
12 | FileName = filename;
13 | DefaultFile = $"{XCOM2.DefaultConfigDir}/Default{FileName}.ini";
14 |
15 | if (load)
16 | Load();
17 | }
18 |
19 | public string FileName { get; }
20 |
21 | ///
22 | /// Default{FileName}.ini from which this one is build
23 | ///
24 | public string DefaultFile { get; set; }
25 |
26 | public void UpdateTimestamp(string baseFile)
27 | {
28 | var numTimestamps = 0;
29 | if (Entries.ContainsKey("IniVersion"))
30 | numTimestamps = Entries["IniVersion"].Count;
31 |
32 | var newTimestamp = new DateTimeOffset(File.GetLastWriteTimeUtc(baseFile)).ToUnixTimeSeconds() + ".000000";
33 |
34 | Add("IniVersion", numTimestamps.ToString(), newTimestamp);
35 | }
36 |
37 |
38 | public new void Load()
39 | {
40 | if (File.Exists(Path))
41 | {
42 | Load(Path);
43 | }
44 | else
45 | {
46 | CreateFromDefault(Path);
47 | Save();
48 | }
49 | }
50 |
51 | public new void Load(string path)
52 | {
53 | base.Load(path);
54 |
55 | if (Entries.ContainsKey("IniVersion") && Entries["IniVersion"].Count > 0)
56 | Entries["IniVersion"].Remove(Entries["IniVersion"].Last().Key);
57 | }
58 |
59 | public new void Save()
60 | {
61 | UpdateTimestamp(DefaultFile);
62 | base.Save();
63 | }
64 |
65 | public void CreateFromDefault(string name)
66 | {
67 | Load(DefaultFile);
68 |
69 | if (!Has("Configuration", "BasedOn"))
70 | return;
71 |
72 |
73 | var baseFile = Get("Configuration", "BasedOn").First();
74 | Remove("Configuration", "BasedOn");
75 |
76 | var file = System.IO.Path.GetFullPath(System.IO.Path.Combine(XCOM2.GameDir, "XComGame", baseFile));
77 |
78 | // Create config from base baseFile
79 | var baseConfig = new IniFile(file, true);
80 |
81 | // Overwrite values
82 | foreach (var section in Entries)
83 | foreach (var entries in section.Value)
84 | {
85 | var op = entries.Key[0];
86 |
87 | switch (op)
88 | {
89 | case '+':
90 | case '.':
91 | foreach (var value in entries.Value)
92 | baseConfig.Add(section.Key, entries.Key.Substring(1), value.Replace("%GAME%", "XCom"));
93 | break;
94 |
95 | case '-':
96 | foreach (var value in entries.Value)
97 | baseConfig.Remove(section.Key, entries.Key.Substring(1), value.Replace("%GAME%", "XCom"));
98 | break;
99 |
100 | case '!':
101 | // !key=ClearArray
102 | baseConfig.Remove(section.Key, entries.Key.Substring(1));
103 | break;
104 |
105 | case ';':
106 | break;
107 |
108 | default:
109 | foreach (var value in entries.Value)
110 | baseConfig.Set(section.Key, entries.Key, new List { value });
111 | break;
112 | }
113 | }
114 |
115 | Entries.Clear();
116 | Entries = baseConfig.Entries;
117 | UpdateTimestamp(file);
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/PropertyGrid/DictionaryPropertyGridAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace XCOM2Launcher.PropertyGrid
10 | {
11 | class DictionaryPropertyGridAdapter : ICustomTypeDescriptor
12 | {
13 | IDictionary _dictionary;
14 |
15 | public DictionaryPropertyGridAdapter(IDictionary d)
16 | {
17 | _dictionary = d;
18 | }
19 |
20 | public string GetComponentName()
21 | {
22 | return TypeDescriptor.GetComponentName(this, true);
23 | }
24 |
25 | public EventDescriptor GetDefaultEvent()
26 | {
27 | return TypeDescriptor.GetDefaultEvent(this, true);
28 | }
29 |
30 | public string GetClassName()
31 | {
32 | return TypeDescriptor.GetClassName(this, true);
33 | }
34 |
35 | public EventDescriptorCollection GetEvents(Attribute[] attributes)
36 | {
37 | return TypeDescriptor.GetEvents(this, attributes, true);
38 | }
39 |
40 | EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
41 | {
42 | return TypeDescriptor.GetEvents(this, true);
43 | }
44 |
45 | public TypeConverter GetConverter()
46 | {
47 | return TypeDescriptor.GetConverter(this, true);
48 | }
49 |
50 | public object GetPropertyOwner(PropertyDescriptor pd)
51 | {
52 | return _dictionary;
53 | }
54 |
55 | public AttributeCollection GetAttributes()
56 | {
57 | return TypeDescriptor.GetAttributes(this, true);
58 | }
59 |
60 | public object GetEditor(Type editorBaseType)
61 | {
62 | return TypeDescriptor.GetEditor(this, editorBaseType, true);
63 | }
64 |
65 | public PropertyDescriptor GetDefaultProperty()
66 | {
67 | return null;
68 | }
69 |
70 | PropertyDescriptorCollection
71 | System.ComponentModel.ICustomTypeDescriptor.GetProperties()
72 | {
73 | return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
74 | }
75 |
76 | public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
77 | {
78 | ArrayList properties = new ArrayList();
79 | foreach (DictionaryEntry e in _dictionary)
80 | {
81 | properties.Add(new DictionaryPropertyDescriptor(_dictionary, e.Key));
82 | }
83 |
84 | PropertyDescriptor[] props =
85 | (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
86 |
87 | return new PropertyDescriptorCollection(props);
88 | }
89 | }
90 |
91 | class DictionaryPropertyDescriptor : PropertyDescriptor
92 | {
93 | IDictionary _dictionary;
94 | object _key;
95 |
96 | internal DictionaryPropertyDescriptor(IDictionary d, object key)
97 | : base(key.ToString(), null)
98 | {
99 | _dictionary = d;
100 | _key = key;
101 | }
102 |
103 | public override Type PropertyType
104 | {
105 | get { return _dictionary[_key].GetType(); }
106 | }
107 |
108 | public override void SetValue(object component, object value)
109 | {
110 | _dictionary[_key] = value;
111 | }
112 |
113 | public override object GetValue(object component)
114 | {
115 | return _dictionary[_key];
116 | }
117 |
118 | public override bool IsReadOnly
119 | {
120 | get { return false; }
121 | }
122 |
123 | public override Type ComponentType
124 | {
125 | get { return null; }
126 | }
127 |
128 | public override bool CanResetValue(object component)
129 | {
130 | return false;
131 | }
132 |
133 | public override void ResetValue(object component)
134 | {
135 | }
136 |
137 | public override bool ShouldSerializeValue(object component)
138 | {
139 | return false;
140 | }
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Mod/ModInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.Contracts;
4 | using System.IO;
5 | using System.Linq;
6 |
7 | namespace XCOM2Launcher.Mod
8 | {
9 | public class ModInfo
10 | {
11 | public ModInfo(string filepath)
12 | {
13 | LoadFile(filepath);
14 | }
15 |
16 | public int PublishedFileID { get; set; } = -1;
17 | public string Title { get; set; }
18 | public string Category { get; set; } = "Unsorted";
19 | public string Description { get; set; } = "";
20 | public string Tags { get; set; } = "";
21 | public string ContentImage { get; set; } = "ModPreview.jpg";
22 |
23 | protected void LoadFile(string filepath)
24 | {
25 | if (!File.Exists(filepath))
26 | return;
27 |
28 | string[] keys = { "publishedfileid", "title", "category", "description", "tags", "contentimage" };
29 | var values = new Dictionary();
30 |
31 | using (var stream = new FileStream(filepath, FileMode.Open))
32 | using (var reader = new StreamReader(stream))
33 | {
34 | string key = null;
35 | string val;
36 |
37 | reader.ReadLine(); // skip [mod] line
38 |
39 | while (!reader.EndOfStream)
40 | {
41 | var line = reader.ReadLine();
42 | Contract.Assume(line != null);
43 |
44 | if (key == null || line.Contains("="))
45 | {
46 | var data = line.Split(new[] { '=' }, 2);
47 | var temp = data[0].Trim().ToLower();
48 |
49 | if (key == null || keys.Contains(temp))
50 | {
51 | // probably right
52 | key = temp;
53 | val = data[1];
54 |
55 | while (line.Last() == '\\')
56 | {
57 | // wow, someone knew what they were doing ?!?!
58 | line = reader.ReadLine();
59 | Contract.Assume(line != null);
60 | val += "\r\n" + line;
61 | }
62 |
63 | if (values.ContainsKey(key))
64 | values[key] = val;
65 | else
66 | values.Add(key, val);
67 | }
68 | else
69 | {
70 | // probably wrong
71 | values[key] += "\r\n" + line;
72 | }
73 | }
74 | else
75 | {
76 | // definitely wrong
77 | values[key] += "\r\n" + line;
78 | }
79 | }
80 | }
81 |
82 |
83 | Title = values["title"];
84 |
85 | if (values.ContainsKey("category") && values["category"].Length > 0)
86 | Category = values["category"];
87 |
88 | try
89 | {
90 | if (values.ContainsKey("publishedfileid"))
91 | PublishedFileID = int.Parse(values["publishedfileid"]);
92 | }
93 | catch (FormatException)
94 | {
95 | PublishedFileID = -1;
96 | }
97 |
98 | if (values.ContainsKey("description"))
99 | Description = values["description"];
100 |
101 | if (values.ContainsKey("tags"))
102 | Tags = values["tags"];
103 |
104 | if (values.ContainsKey("contentimage"))
105 | {
106 | var val = values["contentimage"].Trim('\r', '\n', '\t', ' ');
107 | // todo fix illegal chars?
108 | try
109 | {
110 | var path = Path.GetDirectoryName(filepath);
111 | Contract.Assert(path != null);
112 |
113 | if (val.Length > 0 && File.Exists(Path.Combine(path, val)))
114 | ContentImage = values["contentimage"];
115 | }
116 | catch
117 | {
118 | // ignored
119 | }
120 | }
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/XCOM/XCOM2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 | using Steamworks;
7 | using XCOM2Launcher.Classes.Steam;
8 |
9 | namespace XCOM2Launcher.XCOM
10 | {
11 | public static class XCOM2
12 | {
13 | public const uint APPID = 268500;
14 |
15 | private static string _gameDir;
16 |
17 | public static string GameDir
18 | {
19 | get { return _gameDir ?? (_gameDir = DetectGameDir()); }
20 | set { _gameDir = value; }
21 | }
22 |
23 | public static string UserConfigDir
24 | => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\my games\XCOM2\XComGame\Config";
25 |
26 | public static string DefaultConfigDir => Path.Combine(GameDir, @"XComGame\Config");
27 |
28 | public static string DetectGameDir()
29 | {
30 | // try steam
31 | string gamedir;
32 | if (SteamApps.GetAppInstallDir((AppId_t) APPID, out gamedir, 100) > 0 && Directory.Exists(gamedir))
33 | {
34 | _gameDir = gamedir;
35 | return gamedir;
36 | }
37 |
38 | // try modding dirs
39 | var dirs = DetectModDirs();
40 | foreach (var dir in dirs.Where(dir => dir.ToLower().Contains("\\steamapps\\")))
41 | {
42 | _gameDir = Path.GetFullPath(Path.Combine(dir, "../../..", "common", "XCOM 2"));
43 | return _gameDir;
44 | }
45 |
46 | // abandon hope
47 | return "";
48 | }
49 |
50 | public static void RunGame(string gameDir, string args)
51 | {
52 | if (!SteamAPIWrapper.Init())
53 | MessageBox.Show("Could not connect to steam.");
54 |
55 |
56 | var p = new Process
57 | {
58 | StartInfo =
59 | {
60 | Arguments = args,
61 | FileName = gameDir + @"\Binaries\Win64\XCom2.exe",
62 | WorkingDirectory = gameDir
63 | }
64 | };
65 |
66 | p.Start();
67 |
68 | SteamAPIWrapper.Shutdown();
69 | }
70 |
71 | internal static void ImportActiveMods(Settings settings)
72 | {
73 | // load active mods
74 | foreach (var internalName in GetActiveMods())
75 | foreach (var mod in settings.Mods.All.Where(m => m.ID == internalName))
76 | mod.isActive = true;
77 | }
78 |
79 | public static string[] DetectModDirs()
80 | {
81 |
82 | return
83 | new ConfigFile("Engine").Get("Engine.DownloadableContentEnumerator", "ModRootDirs")?
84 | .Select(
85 | path => Path.IsPathRooted(path)
86 | ? path
87 | : Path.GetFullPath(Path.Combine(GameDir, "bin", "Win64", path))
88 | )
89 | .Where(Directory.Exists)
90 | .ToArray()
91 | ?? new string[0];
92 | }
93 |
94 | public static string[] GetActiveMods()
95 | {
96 | try
97 | {
98 | return new ConfigFile("ModOptions").Get("Engine.XComModOptions", "ActiveMods")?.ToArray() ?? new string[0];
99 | }
100 | catch (IOException)
101 | {
102 | return new string[0];
103 | }
104 | }
105 |
106 | public static void SaveChanges(Settings settings)
107 | {
108 | // XComModOptions
109 | var modOptions = new ConfigFile("ModOptions", false);
110 |
111 | foreach (var m in settings.Mods.Active.OrderBy(m => m.Index))
112 | modOptions.Add("Engine.XComModOptions", "ActiveMods", m.ID);
113 |
114 | modOptions.Save();
115 |
116 | // XComEngine
117 | var engine = new ConfigFile("Engine");
118 |
119 | // Remove old ModClassOverrides
120 | engine.Remove("Engine.Engine", "ModClassOverrides");
121 |
122 | // Set Mod Paths
123 | engine.Remove("Engine.DownloadableContentEnumerator", "ModRootDirs");
124 | foreach (var modPath in settings.ModPaths)
125 | engine.Add("Engine.DownloadableContentEnumerator", "ModRootDirs", modPath);
126 |
127 | // Save
128 | engine.Save();
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/SettingsDialog.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/UpdateAvailableDialog.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/CleanModsForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/UserElements/AutoCompleteTextBox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 |
7 | namespace XCOM2Launcher.UserElements
8 | {
9 | public class AutoCompleteTextBox : TextBox
10 | {
11 | private string _formerValue = string.Empty;
12 | private bool _isAdded;
13 | private ListBox _suggestionsListBox;
14 |
15 | public AutoCompleteTextBox()
16 | {
17 | InitializeComponent();
18 | ResetListBox();
19 | }
20 |
21 | public string[] Values { get; set; }
22 |
23 | public List SelectedValues => Text.Split(' ').Where(str => str.Length > 0).ToList();
24 |
25 | private void InitializeComponent()
26 | {
27 | _suggestionsListBox = new ListBox();
28 | KeyDown += this_KeyDown;
29 | KeyUp += this_KeyUp;
30 | }
31 |
32 | private void ShowListBox()
33 | {
34 | var form = FindForm();
35 | if (form == null)
36 | return;
37 |
38 |
39 | if (!_isAdded)
40 | {
41 | _isAdded = true;
42 | form.Controls.Add(_suggestionsListBox);
43 |
44 | // Move to the top
45 | form.Controls.SetChildIndex(_suggestionsListBox, 0);
46 | }
47 |
48 | // update location
49 | _suggestionsListBox.Location = form.PointToClient(Parent.PointToScreen(Location));
50 |
51 | var lastCharIndex = SelectionStart - GetActiveWord().Length;
52 | var caretPosition = GetPositionFromCharIndex(lastCharIndex);
53 |
54 | _suggestionsListBox.Left += caretPosition.X - 2;
55 | _suggestionsListBox.Top += Height - 1;
56 |
57 | // show
58 | _suggestionsListBox.Visible = true;
59 | }
60 |
61 | private void ResetListBox()
62 | {
63 | _suggestionsListBox.Visible = false;
64 | }
65 |
66 | private void this_KeyUp(object sender, KeyEventArgs e)
67 | {
68 | UpdateListBox();
69 | }
70 |
71 | private void this_KeyDown(object sender, KeyEventArgs e)
72 | {
73 | switch (e.KeyCode)
74 | {
75 | case Keys.Enter:
76 | case Keys.Tab:
77 | if (!_suggestionsListBox.Visible)
78 | break;
79 |
80 | InsertWord((string)_suggestionsListBox.SelectedItem);
81 | ResetListBox();
82 | _formerValue = Text;
83 |
84 | e.Handled = true;
85 | e.SuppressKeyPress = true;
86 | break;
87 |
88 | case Keys.Down:
89 | if (_suggestionsListBox.Visible)
90 | _suggestionsListBox.SelectedIndex = (_suggestionsListBox.SelectedIndex + 1) % _suggestionsListBox.Items.Count;
91 |
92 | e.Handled = true;
93 | e.SuppressKeyPress = true;
94 | break;
95 |
96 | case Keys.Up:
97 | var m = _suggestionsListBox.Items.Count;
98 | if (_suggestionsListBox.Visible)
99 | _suggestionsListBox.SelectedIndex = ((_suggestionsListBox.SelectedIndex - 1) % m + m) % m;
100 |
101 | e.SuppressKeyPress = true;
102 | break;
103 | }
104 | }
105 |
106 | protected override bool IsInputKey(Keys keyData)
107 | {
108 | return keyData == Keys.Tab || base.IsInputKey(keyData);
109 | }
110 |
111 | private void UpdateListBox()
112 | {
113 | if (Text == _formerValue)
114 | return;
115 |
116 |
117 | _formerValue = Text;
118 | var word = GetActiveWord();
119 |
120 | if (word.Length == 0)
121 | {
122 | ResetListBox();
123 | return;
124 | }
125 |
126 | var ignore = SelectedValues;
127 | var matches = Values.Where(x => x.StartsWith(word, StringComparison.OrdinalIgnoreCase) && !ignore.Contains(x, StringComparer.OrdinalIgnoreCase)).OrderBy(str => str).ToList();
128 | if (matches.Count == 0)
129 | {
130 | ResetListBox();
131 | return;
132 | }
133 |
134 | _suggestionsListBox.Items.Clear();
135 |
136 | foreach (var match in matches)
137 | _suggestionsListBox.Items.Add(match);
138 |
139 | _suggestionsListBox.SelectedIndex = 0;
140 |
141 | using (var graphics = _suggestionsListBox.CreateGraphics())
142 | {
143 | _suggestionsListBox.Width = 5 + matches.Max(s => (int) graphics.MeasureString(s, _suggestionsListBox.Font).Width);
144 | _suggestionsListBox.Height = _suggestionsListBox.ItemHeight* (1+ matches.Count);
145 | }
146 |
147 | ShowListBox();
148 | Focus();
149 | }
150 |
151 | private string GetActiveWord()
152 | {
153 | var text = Text;
154 | var pos = SelectionStart;
155 |
156 | var posStart = text.LastIndexOf(' ', pos < 1 ? 0 : pos - 1);
157 | posStart = posStart == -1 ? 0 : posStart + 1;
158 | var posEnd = text.IndexOf(' ', pos);
159 | posEnd = posEnd == -1 ? text.Length : posEnd;
160 |
161 | var length = posEnd - posStart < 0 ? 0 : posEnd - posStart;
162 |
163 | return text.Substring(posStart, length);
164 | }
165 |
166 | private void InsertWord(string newTag)
167 | {
168 | var text = Text;
169 | var pos = SelectionStart;
170 |
171 | var posStart = text.LastIndexOf(' ', pos < 1 ? 0 : pos - 1);
172 | posStart = posStart == -1 ? 0 : posStart + 1;
173 | var posEnd = text.IndexOf(' ', pos);
174 |
175 | var firstPart = text.Substring(0, posStart) + newTag;
176 | var updatedText = firstPart + (posEnd == -1 ? "" : text.Substring(posEnd, text.Length - posEnd));
177 |
178 |
179 | Text = updatedText;
180 | SelectionStart = firstPart.Length;
181 | }
182 | }
183 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/XCOM/IniFile.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 |
6 | namespace XCOM2Launcher.XCOM
7 | {
8 | public class IniFile
9 | {
10 | public IniFile(string path, bool load = false)
11 | {
12 | Path = path;
13 |
14 | if (load)
15 | Load();
16 | }
17 |
18 | public string Path { get; }
19 |
20 |
21 | public Dictionary>> Entries { get; set; } = new Dictionary>>();
22 |
23 | public void Load() => Load(Path);
24 |
25 | public void Load(string file)
26 | {
27 | using (var stream = new FileStream(file, FileMode.Open))
28 | using (var reader = new StreamReader(stream))
29 | {
30 | var currentSection = "";
31 | while (!reader.EndOfStream)
32 | {
33 | var line = reader.ReadLine()?.Trim();
34 |
35 | if (string.IsNullOrEmpty(line))
36 | continue;
37 |
38 | // section header
39 | if (line.StartsWith("[") && line.EndsWith("]"))
40 | {
41 | currentSection = line.Substring(1, line.Length - 2).Trim();
42 | }
43 | else
44 | {
45 | // entries
46 | var pos = line.IndexOf('=');
47 |
48 | if (pos == -1)
49 | // invalid syntax, previous line possibly missing \\
50 | // -> skip
51 | continue;
52 |
53 | var currentKey = line.Substring(0, pos);
54 | var currentValue = line.Substring(pos + 1);
55 |
56 | if (currentKey.StartsWith(";"))
57 | continue;
58 |
59 |
60 | // multi line
61 | while (currentValue.Length > 2 && currentValue.Substring(currentValue.Length - 2) == "\\\\")
62 | currentValue = currentValue.Substring(0, currentValue.Length - 2) + "\n" + reader.ReadLine();
63 |
64 | currentValue = currentValue.Replace("%GAME%", "XCom");
65 |
66 | Add(currentSection, currentKey.TrimEnd(), currentValue.TrimStart());
67 | }
68 | }
69 | }
70 | }
71 |
72 |
73 | public void Save()
74 | {
75 | // Create Dir
76 | var dir = System.IO.Path.GetDirectoryName(Path);
77 | Debug.Assert(dir != null, "dir != null");
78 | if (!Directory.Exists(dir))
79 | Directory.CreateDirectory(dir);
80 |
81 | // Write File
82 | using (var stream = new FileStream(Path, FileMode.Create))
83 | using (var writer = new StreamWriter(stream))
84 | {
85 | foreach (var section in Entries.Where(section => section.Value.Count > 0))
86 | {
87 | writer.WriteLine($"[{section.Key}]");
88 |
89 | foreach (var entry in section.Value)
90 | {
91 | foreach (var val in entry.Value)
92 | {
93 | writer.Write(entry.Key);
94 | writer.Write("=");
95 | writer.Write(val.Replace("\n", "\\\\\n"));
96 | writer.WriteLine();
97 | }
98 | }
99 |
100 | writer.WriteLine();
101 | }
102 | }
103 | }
104 |
105 | public void Set(string section, string key, List value)
106 | {
107 | if (Entries.ContainsKey(section))
108 | // section exists
109 | if (Entries[section].ContainsKey(key))
110 | Entries[section][key] = value;
111 |
112 | else
113 | Entries[section].Add(key, value);
114 |
115 | else
116 | {
117 | var newSection = new Dictionary> { { key, value } };
118 | Entries.Add(section, newSection);
119 | }
120 | }
121 |
122 | public void Add(string section, string key, string value)
123 | {
124 | if (Entries.ContainsKey(section))
125 | // section exists
126 | if (Entries[section].ContainsKey(key))
127 | Entries[section][key].Add(value);
128 |
129 | else
130 | Entries[section].Add(key, new List { value });
131 |
132 | else
133 | {
134 | var newSection = new Dictionary> { { key, new List { value } } };
135 | Entries.Add(section, newSection);
136 | }
137 | }
138 |
139 | public List Get(string section, string key)
140 | {
141 | if (!Has(section, key))
142 | return null;
143 |
144 | return Entries[section][key];
145 | }
146 |
147 | public bool Has(string section) => Entries.ContainsKey(section);
148 | public bool Has(string section, string key) => Has(section) && Entries[section].ContainsKey(key);
149 |
150 |
151 | public bool Remove(string section)
152 | {
153 | return Entries.Remove(section);
154 | }
155 |
156 | public bool Remove(string section, string key)
157 | {
158 | if (!Entries.ContainsKey(section) || !Entries[section].ContainsKey(key))
159 | return false;
160 |
161 | Entries[section][key].Clear();
162 | return true;
163 | }
164 |
165 | public bool Remove(string section, string key, string value)
166 | {
167 | if (!Entries.ContainsKey(section) || !Entries[section].ContainsKey(key))
168 | return false;
169 |
170 | var entry = Entries[section][key];
171 | var count = entry.Count;
172 |
173 | entry.RemoveAll(v => v == value);
174 |
175 | return entry.Count() < count;
176 | }
177 | }
178 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\xcom.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
123 |
124 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Mod/ModEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text.RegularExpressions;
8 | using System.Threading.Tasks;
9 | using Newtonsoft.Json;
10 | using FilePath = System.IO.Path;
11 |
12 | namespace XCOM2Launcher.Mod
13 | {
14 | public class ModEntry
15 | {
16 | [JsonIgnore] private string _image;
17 |
18 | [JsonIgnore] private IEnumerable _overrides;
19 |
20 | ///
21 | /// Index to determine mod load order
22 | ///
23 | [DefaultValue(-1)]
24 | public int Index { get; set; } = -1;
25 |
26 | [JsonIgnore]
27 | public ModState State { get; set; } = ModState.None;
28 |
29 | public string ID { get; set; }
30 | public string Name { get; set; }
31 | public bool ManualName { get; set; } = false;
32 |
33 | public string Author { get; set; } = "Unknown";
34 |
35 | public string Path { get; set; }
36 |
37 | ///
38 | /// Size in bytes
39 | ///
40 | [DefaultValue(-1)]
41 | public long Size { get; set; } = -1;
42 |
43 | public bool isActive { get; set; } = false;
44 | public bool isHidden { get; set; } = false;
45 |
46 | public ModSource Source { get; set; } = ModSource.Unknown;
47 |
48 | [DefaultValue(-1)]
49 | public long WorkshopID { get; set; } = -1;
50 |
51 | public DateTime? DateAdded { get; set; } = null;
52 | public DateTime? DateCreated { get; set; } = null;
53 | public DateTime? DateUpdated { get; set; } = null;
54 |
55 | public string Note { get; set; } = null;
56 |
57 | [JsonIgnore]
58 | public string Image
59 | {
60 | get { return _image ?? FilePath.Combine(Path ?? "", "ModPreview.jpg"); }
61 | set { _image = value; }
62 | }
63 |
64 | public string GetDescription()
65 | {
66 | var info = new ModInfo(GetModInfoFile());
67 |
68 | return info.Description;
69 | }
70 |
71 | public IEnumerable GetOverrides(bool forceUpdate = false)
72 | {
73 | if (_overrides == null || forceUpdate)
74 | {
75 | _overrides = GetUIScreenListenerOverrides().Union(GetClassOverrides()).ToList();
76 | }
77 | return _overrides;
78 | }
79 |
80 | private IEnumerable GetUIScreenListenerOverrides()
81 | {
82 | var sourceDirectory = FilePath.Combine(Path, "Src");
83 | var overrides = new List();
84 |
85 | if (!Directory.Exists(sourceDirectory))
86 | {
87 | return overrides;
88 | }
89 |
90 | var sourceFiles = Directory.GetFiles(sourceDirectory, "*.uc", SearchOption.AllDirectories);
91 |
92 | Parallel.ForEach(sourceFiles, sourceFile =>
93 | {
94 | //The XComGame directory usually contains ALL the source files for the game. Leaving it in is a common mistake.
95 | if (sourceFile.IndexOf(@"Src\XComGame", StringComparison.OrdinalIgnoreCase) != -1)
96 | {
97 | return;
98 | }
99 |
100 | var screenClassRegex = new Regex(@"(?i)^\s*ScreenClass\s*=\s*(?:class')?([a-z_]+)");
101 |
102 | foreach (var line in File.ReadLines(sourceFile))
103 | {
104 | var match = screenClassRegex.Match(line);
105 | if (match.Success)
106 | {
107 | var oldClass = match.Groups[1].Value;
108 | if (oldClass.ToLower() == "none")
109 | {
110 | //'ScreenClass = none' means it runs against every UI screen
111 | continue;
112 | }
113 |
114 | var newClass = FilePath.GetFileNameWithoutExtension(sourceFile);
115 | lock (overrides)
116 | {
117 | overrides.Add(new ModClassOverride(this, newClass, oldClass, ModClassOverrideType.UIScreenListener));
118 | }
119 | }
120 | }
121 | });
122 |
123 | return overrides;
124 | }
125 |
126 | private IEnumerable GetClassOverrides()
127 | {
128 | var file = FilePath.Combine(Path, "Config", "XComEngine.ini");
129 |
130 | if (!File.Exists(file))
131 | return new ModClassOverride[0];
132 |
133 | var r = new Regex("^[+]?ModClassOverrides=\\(BaseGameClass=\"([^\"]+)\",ModClass=\"([^\"]+)\"\\)");
134 |
135 | return from line in File.ReadLines(file)
136 | select r.Match(line.Replace(" ", ""))
137 | into m
138 | where m.Success
139 | select new ModClassOverride(this, m.Groups[2].Value, m.Groups[1].Value, ModClassOverrideType.Class);
140 | }
141 |
142 | public void ShowOnSteam()
143 | {
144 | Process.Start("explorer", "steam://url/CommunityFilePage/" + WorkshopID);
145 | }
146 |
147 | public void ShowInExplorer()
148 | {
149 | Process.Start("explorer", Path);
150 | }
151 |
152 | public string GetWorkshopLink()
153 | {
154 | return "https://steamcommunity.com/sharedfiles/filedetails/?id=" + WorkshopID;
155 | }
156 |
157 | public override string ToString()
158 | {
159 | return ID;
160 | }
161 |
162 | public bool IsInModPath(string modPath)
163 | {
164 | return 0 == string.Compare(modPath.TrimEnd('/', '\\'), FilePath.GetDirectoryName(Path), StringComparison.OrdinalIgnoreCase);
165 | }
166 |
167 | #region Files
168 |
169 | public string[] GetConfigFiles()
170 | {
171 | return Directory.GetFiles(FilePath.Combine(Path, "Config"), "*.ini");
172 | }
173 |
174 | internal string GetModInfoFile()
175 | {
176 | return FilePath.Combine(Path, ID + ".XComMod");
177 | }
178 |
179 | public string GetReadMe()
180 | {
181 | try
182 | {
183 | return File.ReadAllText(FilePath.Combine(Path, "ReadMe.txt"));
184 | }
185 | catch (IOException)
186 | {
187 | return "No ReadMe found.";
188 | }
189 | }
190 |
191 | #endregion
192 | }
193 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
124 | 157, 17
125 |
126 |
127 | 296, 17
128 |
129 |
130 | 37
131 |
132 |
133 | 425, 17
134 |
135 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Windows.Forms;
7 | using Newtonsoft.Json;
8 | using XCOM2Launcher.Classes.Steam;
9 | using XCOM2Launcher.Forms;
10 | using XCOM2Launcher.Mod;
11 | using XCOM2Launcher.XCOM;
12 |
13 | namespace XCOM2Launcher
14 | {
15 | internal static class Program
16 | {
17 | ///
18 | /// Der Haupteinstiegspunkt für die Anwendung.
19 | ///
20 | [STAThread]
21 | private static void Main()
22 | {
23 | #if !DEBUG
24 | try
25 | {
26 | #endif
27 | Application.EnableVisualStyles();
28 | Application.SetCompatibleTextRenderingDefault(false);
29 |
30 | if (!CheckDotNet4_6() && MessageBox.Show("This program requires .NET v4.6 or newer.\r\nDo you want to install it now?", "Error", MessageBoxButtons.YesNo) == DialogResult.Yes)
31 | Process.Start(@"https://www.microsoft.com/de-de/download/details.aspx?id=49981");
32 |
33 | if (!SteamAPIWrapper.Init())
34 | {
35 | MessageBox.Show("Please start steam first!");
36 | return;
37 | }
38 | // SteamWorkshop.StartCallbackService();
39 |
40 |
41 | // Load settings
42 | var settings = InitializeSettings();
43 | if (settings == null)
44 | return;
45 |
46 | #if !DEBUG
47 | // Check for update
48 | if (settings.CheckForUpdates)
49 | {
50 | try
51 | {
52 | using (var client = new System.Net.WebClient())
53 | {
54 | client.Headers.Add("User-Agent: Other");
55 | var json = client.DownloadString("https://api.github.com/repos/aEnigmatic/xcom2-launcher/releases/latest");
56 | var release = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
57 | var currentVersion = GetCurrentVersion();
58 |
59 | if (currentVersion != release.tag_name)
60 | // New version available
61 | new UpdateAvailableDialog(release, currentVersion).ShowDialog();
62 | }
63 | }
64 | catch (System.Net.WebException)
65 | {
66 | // No internet?
67 | }
68 | }
69 | #endif
70 |
71 | // clean up old files
72 | if (File.Exists(XCOM2.DefaultConfigDir + @"\DefaultModOptions.ini.bak"))
73 | {
74 | // Restore backup
75 | File.Copy(XCOM2.DefaultConfigDir + @"\DefaultModOptions.ini.bak", XCOM2.DefaultConfigDir + @"\DefaultModOptions.ini", true);
76 | File.Delete(XCOM2.DefaultConfigDir + @"\DefaultModOptions.ini.bak");
77 | }
78 |
79 | Application.Run(new MainForm(settings));
80 |
81 | SteamAPIWrapper.Shutdown();
82 | #if !DEBUG
83 | }
84 | catch (Exception e)
85 | {
86 | MessageBox.Show("An exception occured. See error.log for additional details.");
87 | File.WriteAllText("error.log", e.Message + "\r\nStack:\r\n" + e.StackTrace);
88 | }
89 | #endif
90 | }
91 |
92 | ///
93 | /// Check whether .net runtime v4.6 is installed
94 | ///
95 | /// bool
96 | private static bool CheckDotNet4_6()
97 | {
98 | try
99 | {
100 | DateTimeOffset.FromUnixTimeSeconds(101010);
101 | return true;
102 | }
103 | catch
104 | {
105 | return false;
106 | }
107 | }
108 |
109 | public static Settings InitializeSettings()
110 | {
111 | var firstRun = !File.Exists("settings.json");
112 |
113 | Settings settings;
114 | if (firstRun)
115 | settings = new Settings();
116 |
117 | else
118 | try
119 | {
120 | settings = Settings.FromFile("settings.json");
121 | }
122 | catch (JsonSerializationException)
123 | {
124 | MessageBox.Show("settings.json could not be read.\r\nPlease delete or rename that file and try again.");
125 | return null;
126 | }
127 |
128 | // Verify Game Path
129 | if (!Directory.Exists(settings.GamePath))
130 | settings.GamePath = XCOM2.DetectGameDir();
131 |
132 | if (settings.GamePath == "")
133 | MessageBox.Show("Could not find XCOM 2 installation path. Please fill it manually in the settings.");
134 |
135 | // Verify Mod Paths
136 | var oldPaths = settings.ModPaths.Where(modPath => !Directory.Exists(modPath)).ToList();
137 | foreach (var modPath in oldPaths)
138 | settings.ModPaths.Remove(modPath);
139 |
140 | foreach (var modPath in XCOM2.DetectModDirs())
141 | if (!settings.ModPaths.Contains(modPath))
142 | settings.ModPaths.Add(modPath);
143 |
144 |
145 | if (settings.ModPaths.Count == 0)
146 | MessageBox.Show("Could not find XCOM 2 mod directories. Please fill them in manually in the settings.");
147 |
148 | if (settings.Mods.Entries.Count > 0)
149 | {
150 | // Verify categories
151 | var index = settings.Mods.Entries.Values.Max(c => c.Index);
152 | foreach (var cat in settings.Mods.Entries.Values.Where(c => c.Index == -1))
153 | cat.Index = ++index;
154 |
155 | // Verify Mods
156 | foreach (var mod in settings.Mods.All.Where(mod => !settings.ModPaths.Any(mod.IsInModPath)))
157 | mod.State |= ModState.NotLoaded;
158 |
159 | var brokenMods = settings.Mods.All.Where(m => !Directory.Exists(m.Path) || !File.Exists(m.GetModInfoFile())).ToList();
160 | if (brokenMods.Count > 0)
161 | {
162 | MessageBox.Show($"{brokenMods.Count} mods no longer exists and have been removed:\r\n\r\n" + string.Join("\r\n", brokenMods.Select(m => m.Name)));
163 |
164 | foreach (var m in brokenMods)
165 | settings.Mods.RemoveMod(m);
166 | }
167 | }
168 |
169 | // import mods
170 | settings.ImportMods();
171 |
172 | return settings;
173 | }
174 |
175 | public static string GetCurrentVersion()
176 | {
177 | var assembly = Assembly.GetExecutingAssembly();
178 | var fields = assembly.GetType("XCOM2Launcher.GitVersionInformation").GetFields();
179 |
180 | var major = fields.Single(f => f.Name == "Major").GetValue(null);
181 | var minor = fields.Single(f => f.Name == "Minor").GetValue(null);
182 | var patch = fields.Single(f => f.Name == "Patch").GetValue(null);
183 |
184 |
185 | return $"v{major}.{minor}.{patch}";
186 | }
187 | }
188 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Steam/SteamManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using System.Collections;
8 | using Steamworks;
9 | using System.Diagnostics;
10 | using XCOM2Launcher.Classes.Steam;
11 |
12 | namespace XCOM2Launcher.Steam
13 | {
14 | // The SteamManager is designed to work with Steamworks.NET
15 | // This file is released into the public domain.
16 | // Where that dedication is not recognized you are granted a perpetual,
17 | // irrevokable license to copy and modify this files as you see fit.
18 | //
19 | // Version: 1.0.3
20 |
21 | //
22 | // The SteamManager provides a base implementation of Steamworks.NET on which you can build upon.
23 | // It handles the basics of starting up and shutting down the SteamAPI for use.
24 | //
25 | public class SteamManager
26 | {
27 | private static SteamManager s_instance;
28 | private static SteamManager Instance
29 | {
30 | get
31 | {
32 | return s_instance ?? new SteamManager();
33 | }
34 | }
35 |
36 | private static bool s_EverInialized;
37 |
38 | private bool m_bInitialized;
39 | public static bool Initialized
40 | {
41 | get
42 | {
43 | return Instance.m_bInitialized;
44 | }
45 | }
46 |
47 | private SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;
48 | private static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText)
49 | {
50 | Debug.Fail(pchDebugText.ToString());
51 | }
52 |
53 | private void Awake()
54 | {
55 | // Only one instance of SteamManager at a time!
56 | if (s_instance != null)
57 | {
58 | //Destroy(gameObject);
59 | return;
60 | }
61 | s_instance = this;
62 |
63 | if (s_EverInialized)
64 | {
65 | // This is almost always an error.
66 | // The most common case where this happens is the SteamManager getting desstroyed via Application.Quit() and having some code in some OnDestroy which gets called afterwards, creating a new SteamManager.
67 | throw new System.Exception("Tried to Initialize the SteamAPI twice in one session!");
68 | }
69 |
70 | // We want our SteamManager Instance to persist across scenes.
71 | //DontDestroyOnLoad(gameObject);
72 |
73 | if (!Packsize.Test())
74 | {
75 | Debug.Fail("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.");
76 | }
77 |
78 | if (!DllCheck.Test())
79 | {
80 | Debug.Fail("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.");
81 | }
82 |
83 | try
84 | {
85 | // If Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
86 | // Steam client and also launches this game again if the User owns it. This can act as a rudimentary form of DRM.
87 |
88 | // Once you get a Steam AppID assigned by Valve, you need to replace AppId_t.Invalid with it and
89 | // remove steam_appid.txt from the game depot. eg: "(AppId_t)480" or "new AppId_t(480)".
90 | // See the Valve documentation for more information: https://partner.steamgames.com/documentation/drm#FAQ
91 | if (SteamAPIWrapper.RestartAppIfNecessary(AppId_t.Invalid))
92 | {
93 | System.Windows.Forms.Application.Exit();
94 | return;
95 | }
96 | }
97 | catch (System.DllNotFoundException e)
98 | { // We catch this exception here, as it will be the first occurence of it.
99 | Debug.Fail("[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" + e);
100 |
101 | System.Windows.Forms.Application.Exit();
102 | return;
103 | }
104 |
105 | // Initialize the SteamAPI, if Init() returns false this can happen for many reasons.
106 | // Some examples include:
107 | // Steam Client is not running.
108 | // Launching from outside of steam without a steam_appid.txt file in place.
109 | // Running under a different OS User or Access level (for example running "as administrator")
110 | // Valve's documentation for this is located here:
111 | // https://partner.steamgames.com/documentation/getting_started
112 | // https://partner.steamgames.com/documentation/example // Under: Common Build Problems
113 | // https://partner.steamgames.com/documentation/bootstrap_stats // At the very bottom
114 |
115 | // If you're running into Init issues try running DbgView prior to launching to get the internal output from Steam.
116 | // http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
117 | m_bInitialized = SteamAPIWrapper.Init();
118 | if (!m_bInitialized)
119 | {
120 | Debug.Fail("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.");
121 |
122 | return;
123 | }
124 |
125 | s_EverInialized = true;
126 | }
127 |
128 | // This should only ever get called on first load and after an Assembly reload, You should never Disable the Steamworks Manager yourself.
129 | private void OnEnable()
130 | {
131 | if (s_instance == null)
132 | {
133 | s_instance = this;
134 | }
135 |
136 | if (!m_bInitialized)
137 | {
138 | return;
139 | }
140 |
141 | if (m_SteamAPIWarningMessageHook == null)
142 | {
143 | // Set up our callback to recieve warning messages from Steam.
144 | // You must launch with "-debug_steamapi" in the launch args to recieve warnings.
145 | m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
146 | SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
147 | }
148 | }
149 |
150 |
151 | // OnApplicationQuit gets called too early to shutdown the SteamAPIWrapper.
152 | // Because the SteamManager should be persistent and never disabled or destroyed we can shutdown the SteamAPI here.
153 | // Thus it is not recommended to perform any Steamworks work in other OnDestroy functions as the order of execution can not be garenteed upon Shutdown. Prefer OnDisable().
154 | private void OnDestroy()
155 | {
156 | if (s_instance != this)
157 | {
158 | return;
159 | }
160 |
161 | s_instance = null;
162 |
163 | if (!m_bInitialized)
164 | {
165 | return;
166 | }
167 |
168 | SteamAPIWrapper.Shutdown();
169 | }
170 |
171 | private void Update()
172 | {
173 | if (!m_bInitialized)
174 | {
175 | return;
176 | }
177 |
178 | // Run Steam client callbacks
179 | SteamAPIWrapper.RunCallbacks();
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/SettingsDialog.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows.Forms;
4 | using Microsoft.VisualBasic;
5 |
6 | namespace XCOM2Launcher.Forms
7 | {
8 | public partial class SettingsDialog : Form
9 | {
10 | public SettingsDialog(Settings settings)
11 | {
12 | InitializeComponent();
13 |
14 | //
15 | Settings = settings;
16 |
17 | // Restore states
18 | gamePathTextBox.Text = settings.GamePath;
19 |
20 | closeAfterLaunchCheckBox.Checked = settings.CloseAfterLaunch;
21 | searchForUpdatesCheckBox.Checked = settings.CheckForUpdates;
22 | showHiddenEntriesCheckBox.Checked = settings.ShowHiddenElements;
23 |
24 | foreach (var modPath in settings.ModPaths)
25 | modPathsListbox.Items.Add(modPath);
26 |
27 | argumentsTextBox.Text = settings.Arguments;
28 | argumentsTextBox.Values = new[]
29 | {
30 | "-Review",
31 | "-NoRedScreens",
32 | "-Log",
33 | "-CrashDumpWatcher",
34 | "-NoStartupMovies",
35 | "-Language=",
36 | "-AllowConsole",
37 | "-AutoDebug"
38 | };
39 |
40 | foreach (var cat in settings.Mods.Categories)
41 | categoriesListBox.Items.Add(cat);
42 |
43 | // Register Events
44 | Shown += SettingsDialog_Shown;
45 | FormClosing += SettingsDialog_FormClosing;
46 |
47 | browseGamePathButton.Click += BrowseGamePathButtonOnClick;
48 |
49 | addModPathButton.Click += AddModPathButtonOnClick;
50 | removeModPathButton.Click += RemoveModPathButtonOnClick;
51 |
52 | moveCategoryDownButton.Click += MoveCategoryDownButtonOnClick;
53 | moveCategoryUpButton.Click += MoveCategoryUpButtonOnClick;
54 |
55 | renameCategoryButton.Click += RenameCategoryButtonOnClick;
56 | removeCategoryButton.Click += RemoveCategoryButtonOnClick;
57 | }
58 |
59 | protected Settings Settings { get; set; }
60 |
61 | private void RemoveCategoryButtonOnClick(object sender, EventArgs eventArgs)
62 | {
63 | var index = categoriesListBox.SelectedIndex;
64 | if (index == -1)
65 | return;
66 |
67 | var category = (string) categoriesListBox.Items[index];
68 |
69 | if (MessageBox.Show($"Are you sure you want to remove the category '{category}'?", "Confirm", MessageBoxButtons.YesNo) == DialogResult.No)
70 | return;
71 |
72 | var entry = Settings.Mods.Entries[category];
73 | foreach (var m in entry.Entries)
74 | Settings.Mods.AddMod("Unsorted", m);
75 |
76 | Settings.Mods.Entries.Remove(category);
77 | categoriesListBox.Items.RemoveAt(index);
78 | }
79 |
80 | private void RenameCategoryButtonOnClick(object sender, EventArgs eventArgs)
81 | {
82 | var index = categoriesListBox.SelectedIndex;
83 | if (index == -1)
84 | return;
85 |
86 | var oldName = (string) categoriesListBox.Items[index];
87 | var newName = Interaction.InputBox($"Enter the new name for the category '{oldName}'");
88 |
89 | categoriesListBox.Items[index] = newName;
90 | var entry = Settings.Mods.Entries[oldName];
91 | Settings.Mods.Entries.Remove(oldName);
92 | Settings.Mods.Entries.Add(newName, entry);
93 | }
94 |
95 | private void MoveCategoryUpButtonOnClick(object sender, EventArgs eventArgs)
96 | {
97 | var index = categoriesListBox.SelectedIndex;
98 | if (index == -1 || index == 0)
99 | return;
100 |
101 |
102 | // Update models
103 | var selectedKey = (string) categoriesListBox.SelectedItem;
104 | var selectedCat = Settings.Mods.Entries[selectedKey];
105 | var prevKey = (string) categoriesListBox.Items[index - 1];
106 | var prevCat = Settings.Mods.Entries[prevKey];
107 |
108 | var temp = selectedCat.Index;
109 | selectedCat.Index = prevCat.Index;
110 | prevCat.Index = temp;
111 |
112 | // Update Interface
113 | categoriesListBox.Items.RemoveAt(index);
114 | categoriesListBox.Items.Insert(index - 1, selectedKey);
115 | categoriesListBox.SelectedIndex = index - 1;
116 | }
117 |
118 | private void MoveCategoryDownButtonOnClick(object sender, EventArgs eventArgs)
119 | {
120 | var index = categoriesListBox.SelectedIndex;
121 | if (index == -1 || index == categoriesListBox.Items.Count - 1)
122 | return;
123 |
124 | // Update models
125 | var selectedKey = (string) categoriesListBox.SelectedItem;
126 | var selectedCat = Settings.Mods.Entries[selectedKey];
127 | var nextKey = (string) categoriesListBox.Items[index + 1];
128 | var nextCat = Settings.Mods.Entries[nextKey];
129 |
130 | var temp = selectedCat.Index;
131 | selectedCat.Index = nextCat.Index;
132 | nextCat.Index = temp;
133 |
134 | // Update Interface
135 | categoriesListBox.Items.RemoveAt(index);
136 | categoriesListBox.Items.Insert(index + 1, selectedKey);
137 | categoriesListBox.SelectedIndex = index + 1;
138 | }
139 |
140 | private void BrowseGamePathButtonOnClick(object sender, EventArgs eventArgs)
141 | {
142 | var dialog = new OpenFileDialog
143 | {
144 | FileName = "XCom2.exe",
145 | Filter = @"XCOM 2 Executable|XCom2.exe",
146 | RestoreDirectory = true,
147 | InitialDirectory = gamePathTextBox.Text
148 | };
149 |
150 | if (dialog.ShowDialog() != DialogResult.OK)
151 | return;
152 |
153 | var path = Path.GetFullPath(Path.Combine(dialog.FileName, "../../.."));
154 | gamePathTextBox.Text = path;
155 | Settings.GamePath = path;
156 | }
157 |
158 | private void RemoveModPathButtonOnClick(object sender, EventArgs e)
159 | {
160 | if (modPathsListbox.SelectedItem == null)
161 | return;
162 |
163 | var path = (string) modPathsListbox.SelectedItem;
164 | modPathsListbox.Items.Remove(path);
165 | Settings.ModPaths.Remove(path);
166 | }
167 |
168 | private void AddModPathButtonOnClick(object sender, EventArgs eventArgs)
169 | {
170 | var dialog = new FolderBrowserDialog
171 | {
172 | ShowNewFolderButton = true,
173 | RootFolder = Environment.SpecialFolder.MyComputer,
174 | Description = "Add a new mod path. Note: This should be the directory that contains the mod directories."
175 | };
176 |
177 | if (dialog.ShowDialog() != DialogResult.OK)
178 | return;
179 |
180 | Settings.ModPaths.Add(dialog.SelectedPath);
181 | modPathsListbox.Items.Add(dialog.SelectedPath);
182 | }
183 |
184 | private void SettingsDialog_Shown(object sender, EventArgs e)
185 | {
186 | // if (Settings.Windows.ContainsKey("settings"))
187 | // Bounds = Settings.Windows["settings"].Bounds;
188 | }
189 |
190 | private void SettingsDialog_FormClosing(object sender, FormClosingEventArgs e)
191 | {
192 | // Save states
193 | Settings.GamePath = Path.GetFullPath(gamePathTextBox.Text);
194 |
195 | Settings.CloseAfterLaunch = closeAfterLaunchCheckBox.Checked;
196 | Settings.CheckForUpdates = searchForUpdatesCheckBox.Checked;
197 | Settings.ShowHiddenElements = showHiddenEntriesCheckBox.Checked;
198 |
199 | Settings.Arguments = argumentsTextBox.Text;
200 |
201 | // Save dimensions
202 | Settings.Windows["settings"] = new WindowSettings(this);
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Classes/Mod/ModList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 | using Newtonsoft.Json;
7 | using Steamworks;
8 | using XCOM2Launcher.Steam;
9 |
10 | namespace XCOM2Launcher.Mod
11 | {
12 | public class ModList
13 | {
14 | public Dictionary Entries { get; } = new Dictionary();
15 |
16 | [JsonIgnore]
17 | public IEnumerable All => Entries.SelectMany(c => c.Value.Entries);
18 |
19 | [JsonIgnore]
20 | public IEnumerable Categories => Entries.OrderBy(c => c.Value.Index).ThenBy(c => c.Key).Select(c => c.Key);
21 |
22 | [JsonIgnore]
23 | public IEnumerable Active => All.Where(m => m.isActive);
24 |
25 | public virtual ModCategory this[string category]
26 | {
27 | get
28 | {
29 | ModCategory cat;
30 | Entries.TryGetValue(category, out cat);
31 |
32 | if (cat == null)
33 | {
34 | cat = new ModCategory();
35 | Entries.Add(category, cat);
36 | }
37 |
38 | return cat;
39 | }
40 | }
41 |
42 | public ModEntry FindByPath(string path)
43 | {
44 | return All.SingleOrDefault(m => string.Compare(m.Path, path, StringComparison.OrdinalIgnoreCase) == 0);
45 | }
46 |
47 | public IEnumerable GetActiveConflicts()
48 | {
49 | var activeConflicts = GetActiveConflictsImplementation().ToList();
50 | UpdateModsConflictState(activeConflicts);
51 | return activeConflicts;
52 | }
53 |
54 | private IEnumerable GetActiveConflictsImplementation()
55 | {
56 | IEnumerable allOverrides = Active.SelectMany(o => o.GetOverrides()).ToList();
57 | var classesOverriden = allOverrides
58 | .Select(o => o.OldClass)
59 | .Distinct(StringComparer.InvariantCultureIgnoreCase);
60 |
61 | return from className in classesOverriden
62 | let overridesForThisClass = allOverrides.Where(o =>
63 | o.OldClass.Equals(className, StringComparison.InvariantCultureIgnoreCase)).ToList()
64 | where overridesForThisClass.Count > 1
65 | && overridesForThisClass.Any(o => o.OverrideType == ModClassOverrideType.Class)
66 | //If every mod uses a UIScreenListener, there is no conflict
67 | select new ModConflict(className, overridesForThisClass);
68 | }
69 |
70 | private void UpdateModsConflictState(IEnumerable activeConflicts)
71 | {
72 | foreach (var mod in All)
73 | {
74 | mod.State &= ~ModState.ModConflict;
75 | }
76 |
77 | foreach (var classOverride in activeConflicts.SelectMany(conflict => conflict.Overrides))
78 | {
79 | classOverride.Mod.State |= ModState.ModConflict;
80 | }
81 | }
82 |
83 | public void ImportMods(string dir)
84 | {
85 | if (!Directory.Exists(dir))
86 | return;
87 |
88 | // (try to) load mods
89 | foreach (var modDir in Directory.GetDirectories(dir))
90 | {
91 | var source = modDir.IndexOf(@"\SteamApps\workshop\", StringComparison.OrdinalIgnoreCase) != -1
92 | ? ModSource.SteamWorkshop
93 | : ModSource.Manual;
94 |
95 | Import(modDir, source);
96 | }
97 | }
98 |
99 | public ModEntry Import(string modDir, ModSource source = ModSource.Unknown)
100 | {
101 | if (FindByPath(modDir) != null)
102 | // Mod already loaded
103 | return null;
104 |
105 | // look for .XComMod file
106 | string infoFile;
107 | try
108 | {
109 | infoFile = Directory.GetFiles(modDir, "*.XComMod", SearchOption.TopDirectoryOnly).SingleOrDefault();
110 | }
111 | catch (InvalidOperationException)
112 | {
113 | MessageBox.Show(
114 | $"A mod could not be loaded since it contains multiple .xcommod files\r\nPlease notify the mod creator.\r\n\r\nPath: {modDir}");
115 | return null;
116 | }
117 |
118 | if (infoFile == null)
119 | return null;
120 |
121 | var modID = Path.GetFileNameWithoutExtension(infoFile);
122 | var isDupe = All.Any(m => m.ID == modID && string.Compare(m.Path, modDir, StringComparison.OrdinalIgnoreCase) == 0);
123 |
124 | // Parse .XComMod file
125 | var modinfo = new ModInfo(infoFile);
126 |
127 | var mod = new ModEntry
128 | {
129 | ID = modID,
130 | Name = modinfo.Title ?? "Unnamed Mod",
131 | Path = modDir,
132 | Source = source,
133 | isActive = false,
134 | DateAdded = DateTime.Now,
135 | State = ModState.New
136 | };
137 |
138 | AddMod(modinfo.Category, mod);
139 |
140 | // mark dupes
141 | if (isDupe)
142 | foreach (var m in All.Where(m => m.ID == modID))
143 | m.State |= ModState.DuplicateID;
144 |
145 | return mod;
146 | }
147 |
148 | public void AddMod(string category, ModEntry mod)
149 | {
150 | if (mod.Index == -1)
151 | mod.Index = All.Count();
152 | this[category].Entries.Add(mod);
153 | }
154 |
155 | public void RemoveMod(ModEntry mod)
156 | {
157 | var category = GetCategory(mod);
158 | this[category].Entries.Remove(mod);
159 | }
160 |
161 | internal void UpdateMod(ModEntry m, Settings settings)
162 | {
163 | // Check if in ModPaths
164 | if (!settings.ModPaths.Any(modPath => m.IsInModPath(modPath)))
165 | m.State |= ModState.NotLoaded;
166 |
167 | // Update Source
168 | if (m.Source == ModSource.Unknown)
169 | {
170 | if (m.Path.IndexOf(@"\SteamApps\workshop\", StringComparison.OrdinalIgnoreCase) != -1)
171 | m.Source = ModSource.SteamWorkshop;
172 |
173 | else
174 | // in workshop path but not loaded via steam
175 | m.Source = ModSource.Manual;
176 | }
177 |
178 | // Ensure source ID exists
179 | if (m.WorkshopID <= 0)
180 | {
181 | long sourceID;
182 |
183 | if (m.Source == ModSource.SteamWorkshop && long.TryParse(Path.GetFileName(m.Path), out sourceID))
184 | m.Source = ModSource.Manual;
185 |
186 | else
187 | sourceID = new ModInfo(m.GetModInfoFile()).PublishedFileID;
188 |
189 | m.WorkshopID = sourceID;
190 | }
191 |
192 | // Fill Date Added
193 | if (!m.DateAdded.HasValue)
194 | m.DateAdded = DateTime.Now;
195 |
196 |
197 | // Check Workshop for infos
198 | if (m.WorkshopID != -1)
199 | {
200 | var publishedID = (ulong) m.WorkshopID;
201 |
202 | var value = Workshop.GetDetails(publishedID);
203 |
204 | if (!m.ManualName)
205 | m.Name = value.m_rgchTitle;
206 |
207 | m.DateCreated = DateTimeOffset.FromUnixTimeSeconds(value.m_rtimeCreated).DateTime;
208 | m.DateUpdated = DateTimeOffset.FromUnixTimeSeconds(value.m_rtimeUpdated).DateTime;
209 |
210 | if (value.m_rtimeAddedToUserList > 0)
211 | m.DateAdded = DateTimeOffset.FromUnixTimeSeconds(value.m_rtimeAddedToUserList).DateTime;
212 |
213 | //m.Author = SteamWorkshop.GetUsername(value.m_ulSteamIDOwner);
214 | //MessageBox.Show(m.Author);
215 |
216 | // Update directory size
217 | m.Size = value.m_nFileSize;
218 |
219 | // Check Workshop for updates
220 | if (m.Source == ModSource.SteamWorkshop)
221 | if (
222 | Workshop.GetDownloadStatus((ulong) m.WorkshopID)
223 | .HasFlag(EItemState.k_EItemStateNeedsUpdate))
224 | m.State |= ModState.UpdateAvailable;
225 | }
226 | else
227 | {
228 | // Update directory size
229 | // slow, but necessary ?
230 | m.Size = Directory.EnumerateFiles(m.Path, "*", SearchOption.AllDirectories).Sum(fileName => new FileInfo(fileName).Length);
231 | }
232 | }
233 |
234 | public string GetCategory(ModEntry mod)
235 | {
236 | return Entries.First(entry => entry.Value.Entries.Contains(mod)).Key;
237 | }
238 |
239 | public override string ToString()
240 | {
241 | return $"{All.Count()} mods in {Entries.Count} categories";
242 | }
243 |
244 | public IEnumerable> GetDuplicates()
245 | {
246 | return All.GroupBy(m => m.ID).Where(g => g.Count() > 1);
247 | }
248 |
249 | public void MarkDuplicates()
250 | {
251 | foreach (var m in GetDuplicates().SelectMany(@group => @group))
252 | m.State |= ModState.DuplicateID;
253 | }
254 | }
255 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/xcom2-launcher.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {FC343C56-9BC8-483A-A254-3484B2137E24}
8 | WinExe
9 | Properties
10 | XCOM2Launcher
11 | XCOM2 Launcher
12 | v4.6
13 | 512
14 |
15 | publish\
16 | true
17 | Disk
18 | false
19 | Foreground
20 | 7
21 | Days
22 | false
23 | false
24 | true
25 | 0
26 | 1.0.0.%2a
27 | false
28 | false
29 | true
30 |
31 |
32 |
33 |
34 | AnyCPU
35 | true
36 | full
37 | false
38 | bin\Debug\
39 | DEBUG;TRACE
40 | prompt
41 | 4
42 |
43 |
44 | AnyCPU
45 | pdbonly
46 | true
47 | bin\Release\
48 | TRACE
49 | prompt
50 | 4
51 |
52 |
53 | true
54 | bin\x64\Debug\
55 | DEBUG;TRACE
56 | full
57 | x64
58 | prompt
59 | MinimumRecommendedRules.ruleset
60 | true
61 |
62 |
63 | bin\x64\Release\
64 | TRACE
65 | true
66 | pdbonly
67 | x64
68 | prompt
69 | MinimumRecommendedRules.ruleset
70 | true
71 |
72 |
73 | true
74 |
75 |
76 | XCOM2Launcher.Program
77 |
78 |
79 | Resources\xcom.ico
80 |
81 |
82 |
83 |
84 | ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll
85 | True
86 |
87 |
88 | ..\packages\ObjectListView.Official.2.9.0\lib\net20\ObjectListView.dll
89 | True
90 |
91 |
92 | False
93 | .\Steamworks.NET.dll
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 | Form
135 |
136 |
137 | CleanModsForm.cs
138 |
139 |
140 | Form
141 |
142 |
143 | MainForm.cs
144 | Form
145 |
146 |
147 | MainForm.cs
148 | Form
149 |
150 |
151 | MainForm.cs
152 |
153 |
154 | Form
155 |
156 |
157 | SettingsDialog.cs
158 |
159 |
160 |
161 | Form
162 |
163 |
164 | UpdateAvailableDialog.cs
165 |
166 |
167 |
168 |
169 | Component
170 |
171 |
172 | CleanModsForm.cs
173 |
174 |
175 | MainForm.cs
176 |
177 |
178 | SettingsDialog.cs
179 |
180 |
181 | UpdateAvailableDialog.cs
182 |
183 |
184 | ResXFileCodeGenerator
185 | Resources.Designer.cs
186 | Designer
187 |
188 |
189 | True
190 | Resources.resx
191 | True
192 |
193 |
194 |
195 |
196 |
197 | SettingsSingleFileGenerator
198 | Settings.Designer.cs
199 |
200 |
201 | True
202 | Settings.settings
203 | True
204 |
205 |
206 |
207 |
208 | PreserveNewest
209 |
210 |
211 | PreserveNewest
212 |
213 |
214 | PreserveNewest
215 |
216 |
217 | PreserveNewest
218 |
219 |
220 |
221 |
222 |
223 |
224 | False
225 | Microsoft .NET Framework 4.6 %28x86 and x64%29
226 | true
227 |
228 |
229 | False
230 | .NET Framework 3.5 SP1
231 | false
232 |
233 |
234 |
235 |
236 |
237 |
238 | Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".
239 |
240 |
241 |
242 |
249 |
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/CleanModsForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace XCOM2Launcher.Forms
2 | {
3 | partial class CleanModsForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.components = new System.ComponentModel.Container();
32 | this.button1 = new System.Windows.Forms.Button();
33 | this.groupBox1 = new System.Windows.Forms.GroupBox();
34 | this.shader_groupbox = new System.Windows.Forms.GroupBox();
35 | this.shadercache_none_radiobutton = new System.Windows.Forms.RadioButton();
36 | this.shadercache_empty_radiobutton = new System.Windows.Forms.RadioButton();
37 | this.shadercache_all_radiobutton = new System.Windows.Forms.RadioButton();
38 | this.button3 = new System.Windows.Forms.Button();
39 | this.source_groupbox = new System.Windows.Forms.GroupBox();
40 | this.src_all_radiobutton = new System.Windows.Forms.RadioButton();
41 | this.src_xcomgame_radiobutton = new System.Windows.Forms.RadioButton();
42 | this.src_none_radiobutton = new System.Windows.Forms.RadioButton();
43 | this.button2 = new System.Windows.Forms.Button();
44 | this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
45 | this.groupBox1.SuspendLayout();
46 | this.shader_groupbox.SuspendLayout();
47 | this.source_groupbox.SuspendLayout();
48 | this.SuspendLayout();
49 | //
50 | // button1
51 | //
52 | this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
53 | this.button1.Location = new System.Drawing.Point(296, 135);
54 | this.button1.Name = "button1";
55 | this.button1.Size = new System.Drawing.Size(75, 23);
56 | this.button1.TabIndex = 7;
57 | this.button1.Text = "Start";
58 | this.button1.UseVisualStyleBackColor = true;
59 |
60 | //
61 | // groupBox1
62 | //
63 | this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
64 | | System.Windows.Forms.AnchorStyles.Right)));
65 | this.groupBox1.Controls.Add(this.shader_groupbox);
66 | this.groupBox1.Controls.Add(this.source_groupbox);
67 | this.groupBox1.Location = new System.Drawing.Point(12, 12);
68 | this.groupBox1.Name = "groupBox1";
69 | this.groupBox1.Size = new System.Drawing.Size(359, 116);
70 | this.groupBox1.TabIndex = 3;
71 | this.groupBox1.TabStop = false;
72 | this.groupBox1.Text = "Deletion settings";
73 | //
74 | // shader_groupbox
75 | //
76 | this.shader_groupbox.Controls.Add(this.shadercache_none_radiobutton);
77 | this.shader_groupbox.Controls.Add(this.shadercache_empty_radiobutton);
78 | this.shader_groupbox.Controls.Add(this.shadercache_all_radiobutton);
79 | this.shader_groupbox.Controls.Add(this.button3);
80 | this.shader_groupbox.Location = new System.Drawing.Point(182, 19);
81 | this.shader_groupbox.Name = "shader_groupbox";
82 | this.shader_groupbox.Size = new System.Drawing.Size(170, 91);
83 | this.shader_groupbox.TabIndex = 6;
84 | this.shader_groupbox.TabStop = false;
85 | this.shader_groupbox.Text = "ModShaderCache files";
86 | //
87 | // shadercache_none_radiobutton
88 | //
89 | this.shadercache_none_radiobutton.AutoSize = true;
90 | this.shadercache_none_radiobutton.Location = new System.Drawing.Point(6, 19);
91 | this.shadercache_none_radiobutton.Name = "shadercache_none_radiobutton";
92 | this.shadercache_none_radiobutton.Size = new System.Drawing.Size(51, 17);
93 | this.shadercache_none_radiobutton.TabIndex = 4;
94 | this.shadercache_none_radiobutton.Text = "None";
95 | this.toolTip1.SetToolTip(this.shadercache_none_radiobutton, "Do not delete anything.");
96 | this.shadercache_none_radiobutton.UseVisualStyleBackColor = true;
97 | //
98 | // shadercache_empty_radiobutton
99 | //
100 | this.shadercache_empty_radiobutton.AutoSize = true;
101 | this.shadercache_empty_radiobutton.Checked = true;
102 | this.shadercache_empty_radiobutton.Location = new System.Drawing.Point(6, 42);
103 | this.shadercache_empty_radiobutton.Name = "shadercache_empty_radiobutton";
104 | this.shadercache_empty_radiobutton.Size = new System.Drawing.Size(54, 17);
105 | this.shadercache_empty_radiobutton.TabIndex = 5;
106 | this.shadercache_empty_radiobutton.TabStop = true;
107 | this.shadercache_empty_radiobutton.Text = "Empty";
108 | this.toolTip1.SetToolTip(this.shadercache_empty_radiobutton, "Safe.");
109 | this.shadercache_empty_radiobutton.UseVisualStyleBackColor = true;
110 | //
111 | // shadercache_all_radiobutton
112 | //
113 | this.shadercache_all_radiobutton.AutoSize = true;
114 | this.shadercache_all_radiobutton.Location = new System.Drawing.Point(6, 65);
115 | this.shadercache_all_radiobutton.Name = "shadercache_all_radiobutton";
116 | this.shadercache_all_radiobutton.Size = new System.Drawing.Size(36, 17);
117 | this.shadercache_all_radiobutton.TabIndex = 6;
118 | this.shadercache_all_radiobutton.Text = "All";
119 | this.toolTip1.SetToolTip(this.shadercache_all_radiobutton, "NOT SAFE. This can/will cause graphical glitches.");
120 | this.shadercache_all_radiobutton.UseVisualStyleBackColor = true;
121 | //
122 | // button3
123 | //
124 | this.button3.Location = new System.Drawing.Point(303, 75);
125 | this.button3.Name = "button3";
126 | this.button3.Size = new System.Drawing.Size(75, 23);
127 | this.button3.TabIndex = 0;
128 | this.button3.Text = "Start";
129 | this.button3.UseVisualStyleBackColor = true;
130 | //
131 | // source_groupbox
132 | //
133 | this.source_groupbox.Controls.Add(this.src_all_radiobutton);
134 | this.source_groupbox.Controls.Add(this.src_xcomgame_radiobutton);
135 | this.source_groupbox.Controls.Add(this.src_none_radiobutton);
136 | this.source_groupbox.Controls.Add(this.button2);
137 | this.source_groupbox.Location = new System.Drawing.Point(6, 19);
138 | this.source_groupbox.Name = "source_groupbox";
139 | this.source_groupbox.Size = new System.Drawing.Size(170, 91);
140 | this.source_groupbox.TabIndex = 4;
141 | this.source_groupbox.TabStop = false;
142 | this.source_groupbox.Text = "Source files";
143 | this.toolTip1.SetToolTip(this.source_groupbox, "Source files are only relevant for mod creators.");
144 | //
145 | // src_all_radiobutton
146 | //
147 | this.src_all_radiobutton.AutoSize = true;
148 | this.src_all_radiobutton.Location = new System.Drawing.Point(6, 19);
149 | this.src_all_radiobutton.Name = "src_all_radiobutton";
150 | this.src_all_radiobutton.Size = new System.Drawing.Size(51, 17);
151 | this.src_all_radiobutton.TabIndex = 1;
152 | this.src_all_radiobutton.Text = "None";
153 | this.toolTip1.SetToolTip(this.src_all_radiobutton, "Do not delete anything.");
154 | this.src_all_radiobutton.UseVisualStyleBackColor = true;
155 | //
156 | // src_xcomgame_radiobutton
157 | //
158 | this.src_xcomgame_radiobutton.AutoSize = true;
159 | this.src_xcomgame_radiobutton.Checked = true;
160 | this.src_xcomgame_radiobutton.Location = new System.Drawing.Point(6, 42);
161 | this.src_xcomgame_radiobutton.Name = "src_xcomgame_radiobutton";
162 | this.src_xcomgame_radiobutton.Size = new System.Drawing.Size(81, 17);
163 | this.src_xcomgame_radiobutton.TabIndex = 2;
164 | this.src_xcomgame_radiobutton.TabStop = true;
165 | this.src_xcomgame_radiobutton.Text = "XComGame";
166 | this.toolTip1.SetToolTip(this.src_xcomgame_radiobutton, "Only delete src/XComGame files.");
167 | this.src_xcomgame_radiobutton.UseVisualStyleBackColor = true;
168 | //
169 | // src_none_radiobutton
170 | //
171 | this.src_none_radiobutton.AutoSize = true;
172 | this.src_none_radiobutton.Location = new System.Drawing.Point(6, 65);
173 | this.src_none_radiobutton.Name = "src_none_radiobutton";
174 | this.src_none_radiobutton.Size = new System.Drawing.Size(36, 17);
175 | this.src_none_radiobutton.TabIndex = 3;
176 | this.src_none_radiobutton.Text = "All";
177 | this.toolTip1.SetToolTip(this.src_none_radiobutton, "Delete all source files.");
178 | this.src_none_radiobutton.UseVisualStyleBackColor = true;
179 | //
180 | // button2
181 | //
182 | this.button2.Location = new System.Drawing.Point(303, 75);
183 | this.button2.Name = "button2";
184 | this.button2.Size = new System.Drawing.Size(75, 23);
185 | this.button2.TabIndex = 0;
186 | this.button2.Text = "Start";
187 | this.button2.UseVisualStyleBackColor = true;
188 | //
189 | // CleanModsForm
190 | //
191 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
192 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
193 | this.ClientSize = new System.Drawing.Size(383, 165);
194 | this.Controls.Add(this.button1);
195 | this.Controls.Add(this.groupBox1);
196 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
197 | this.Icon = global::XCOM2Launcher.Properties.Resources.xcom;
198 | this.Name = "CleanModsForm";
199 | this.Text = "Clean mods";
200 | this.groupBox1.ResumeLayout(false);
201 | this.shader_groupbox.ResumeLayout(false);
202 | this.shader_groupbox.PerformLayout();
203 | this.source_groupbox.ResumeLayout(false);
204 | this.source_groupbox.PerformLayout();
205 | this.ResumeLayout(false);
206 |
207 | }
208 |
209 | #endregion
210 |
211 | private System.Windows.Forms.Button button1;
212 | private System.Windows.Forms.GroupBox groupBox1;
213 | private System.Windows.Forms.GroupBox source_groupbox;
214 | private System.Windows.Forms.RadioButton src_xcomgame_radiobutton;
215 | private System.Windows.Forms.RadioButton src_none_radiobutton;
216 | private System.Windows.Forms.Button button2;
217 | private System.Windows.Forms.RadioButton src_all_radiobutton;
218 | private System.Windows.Forms.GroupBox shader_groupbox;
219 | private System.Windows.Forms.RadioButton shadercache_none_radiobutton;
220 | private System.Windows.Forms.RadioButton shadercache_empty_radiobutton;
221 | private System.Windows.Forms.RadioButton shadercache_all_radiobutton;
222 | private System.Windows.Forms.Button button3;
223 | private System.Windows.Forms.ToolTip toolTip1;
224 | }
225 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/UpdateAvailableDialog.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace XCOM2Launcher.Forms
2 | {
3 | partial class UpdateAvailableDialog
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.version_current_label = new System.Windows.Forms.Label();
32 | this.changelog_textbox = new System.Windows.Forms.TextBox();
33 | this.version_new_label = new System.Windows.Forms.Label();
34 | this.version_current_value_label = new System.Windows.Forms.Label();
35 | this.version_new_value_label = new System.Windows.Forms.Label();
36 | this.changelog_label = new System.Windows.Forms.Label();
37 | this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
38 | this.filesize_label = new System.Windows.Forms.Label();
39 | this.filesize_value_label = new System.Windows.Forms.Label();
40 | this.date_label = new System.Windows.Forms.Label();
41 | this.date_value_label = new System.Windows.Forms.Label();
42 | this.show_button = new System.Windows.Forms.Button();
43 | this.close_button = new System.Windows.Forms.Button();
44 | this.tableLayoutPanel1.SuspendLayout();
45 | this.SuspendLayout();
46 | //
47 | // version_current_label
48 | //
49 | this.version_current_label.AutoSize = true;
50 | this.version_current_label.Location = new System.Drawing.Point(3, 0);
51 | this.version_current_label.Name = "version_current_label";
52 | this.version_current_label.Size = new System.Drawing.Size(78, 13);
53 | this.version_current_label.TabIndex = 2;
54 | this.version_current_label.Text = "Current version";
55 | //
56 | // changelog_textbox
57 | //
58 | this.changelog_textbox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
59 | | System.Windows.Forms.AnchorStyles.Left)
60 | | System.Windows.Forms.AnchorStyles.Right)));
61 | this.changelog_textbox.Location = new System.Drawing.Point(12, 89);
62 | this.changelog_textbox.Multiline = true;
63 | this.changelog_textbox.Name = "changelog_textbox";
64 | this.changelog_textbox.Size = new System.Drawing.Size(416, 103);
65 | this.changelog_textbox.TabIndex = 4;
66 | //
67 | // version_new_label
68 | //
69 | this.version_new_label.AutoSize = true;
70 | this.version_new_label.Location = new System.Drawing.Point(3, 29);
71 | this.version_new_label.Name = "version_new_label";
72 | this.version_new_label.Size = new System.Drawing.Size(66, 13);
73 | this.version_new_label.TabIndex = 6;
74 | this.version_new_label.Text = "New version";
75 | //
76 | // version_current_value_label
77 | //
78 | this.version_current_value_label.AutoSize = true;
79 | this.version_current_value_label.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
80 | this.version_current_value_label.Location = new System.Drawing.Point(103, 0);
81 | this.version_current_value_label.Name = "version_current_value_label";
82 | this.version_current_value_label.Size = new System.Drawing.Size(41, 13);
83 | this.version_current_value_label.TabIndex = 7;
84 | this.version_current_value_label.Text = "label1";
85 | //
86 | // version_new_value_label
87 | //
88 | this.version_new_value_label.AutoSize = true;
89 | this.version_new_value_label.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
90 | this.version_new_value_label.Location = new System.Drawing.Point(103, 29);
91 | this.version_new_value_label.Name = "version_new_value_label";
92 | this.version_new_value_label.Size = new System.Drawing.Size(41, 13);
93 | this.version_new_value_label.TabIndex = 7;
94 | this.version_new_value_label.Text = "label1";
95 | //
96 | // changelog_label
97 | //
98 | this.changelog_label.AutoSize = true;
99 | this.changelog_label.Location = new System.Drawing.Point(12, 73);
100 | this.changelog_label.Name = "changelog_label";
101 | this.changelog_label.Size = new System.Drawing.Size(63, 13);
102 | this.changelog_label.TabIndex = 8;
103 | this.changelog_label.Text = "What\'s new";
104 | //
105 | // tableLayoutPanel1
106 | //
107 | this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
108 | | System.Windows.Forms.AnchorStyles.Right)));
109 | this.tableLayoutPanel1.ColumnCount = 4;
110 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F));
111 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 82F));
112 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 46F));
113 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 188F));
114 | this.tableLayoutPanel1.Controls.Add(this.filesize_label, 2, 0);
115 | this.tableLayoutPanel1.Controls.Add(this.version_current_label, 0, 0);
116 | this.tableLayoutPanel1.Controls.Add(this.version_new_value_label, 1, 1);
117 | this.tableLayoutPanel1.Controls.Add(this.version_new_label, 0, 1);
118 | this.tableLayoutPanel1.Controls.Add(this.version_current_value_label, 1, 0);
119 | this.tableLayoutPanel1.Controls.Add(this.filesize_value_label, 3, 0);
120 | this.tableLayoutPanel1.Controls.Add(this.date_label, 2, 1);
121 | this.tableLayoutPanel1.Controls.Add(this.date_value_label, 3, 1);
122 | this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 12);
123 | this.tableLayoutPanel1.Name = "tableLayoutPanel1";
124 | this.tableLayoutPanel1.RowCount = 2;
125 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
126 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
127 | this.tableLayoutPanel1.Size = new System.Drawing.Size(416, 58);
128 | this.tableLayoutPanel1.TabIndex = 9;
129 | //
130 | // filesize_label
131 | //
132 | this.filesize_label.AutoSize = true;
133 | this.filesize_label.Location = new System.Drawing.Point(185, 0);
134 | this.filesize_label.Name = "filesize_label";
135 | this.filesize_label.Size = new System.Drawing.Size(27, 13);
136 | this.filesize_label.TabIndex = 10;
137 | this.filesize_label.Text = "Size";
138 | //
139 | // filesize_value_label
140 | //
141 | this.filesize_value_label.AutoSize = true;
142 | this.filesize_value_label.Location = new System.Drawing.Point(231, 0);
143 | this.filesize_value_label.Name = "filesize_value_label";
144 | this.filesize_value_label.Size = new System.Drawing.Size(35, 13);
145 | this.filesize_value_label.TabIndex = 11;
146 | this.filesize_value_label.Text = "label1";
147 | //
148 | // date_label
149 | //
150 | this.date_label.AutoSize = true;
151 | this.date_label.Location = new System.Drawing.Point(185, 29);
152 | this.date_label.Name = "date_label";
153 | this.date_label.Size = new System.Drawing.Size(30, 13);
154 | this.date_label.TabIndex = 10;
155 | this.date_label.Text = "Date";
156 | //
157 | // date_value_label
158 | //
159 | this.date_value_label.AutoSize = true;
160 | this.date_value_label.Location = new System.Drawing.Point(231, 29);
161 | this.date_value_label.Name = "date_value_label";
162 | this.date_value_label.Size = new System.Drawing.Size(35, 13);
163 | this.date_value_label.TabIndex = 11;
164 | this.date_value_label.Text = "label1";
165 | //
166 | // show_button
167 | //
168 | this.show_button.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
169 | this.show_button.Location = new System.Drawing.Point(246, 198);
170 | this.show_button.Name = "show_button";
171 | this.show_button.Size = new System.Drawing.Size(101, 23);
172 | this.show_button.TabIndex = 10;
173 | this.show_button.Text = "Show on GitHub";
174 | this.show_button.UseVisualStyleBackColor = true;
175 | this.show_button.Click += new System.EventHandler(this.show_button_Click);
176 | //
177 | // close_button
178 | //
179 | this.close_button.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
180 | this.close_button.DialogResult = System.Windows.Forms.DialogResult.Cancel;
181 | this.close_button.Location = new System.Drawing.Point(353, 198);
182 | this.close_button.Name = "close_button";
183 | this.close_button.Size = new System.Drawing.Size(75, 23);
184 | this.close_button.TabIndex = 1;
185 | this.close_button.Text = "Close";
186 | this.close_button.UseVisualStyleBackColor = true;
187 | this.close_button.Click += new System.EventHandler(this.close_button_Click);
188 | //
189 | // UpdateAvailableDialog
190 | //
191 | this.AcceptButton = this.show_button;
192 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
193 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
194 | this.ClientSize = new System.Drawing.Size(440, 233);
195 | this.Controls.Add(this.show_button);
196 | this.Controls.Add(this.changelog_label);
197 | this.Controls.Add(this.changelog_textbox);
198 | this.Controls.Add(this.close_button);
199 | this.Controls.Add(this.tableLayoutPanel1);
200 | this.Name = "UpdateAvailableDialog";
201 | this.Text = "Update available!";
202 | this.tableLayoutPanel1.ResumeLayout(false);
203 | this.tableLayoutPanel1.PerformLayout();
204 | this.ResumeLayout(false);
205 | this.PerformLayout();
206 |
207 | }
208 |
209 | #endregion
210 | private System.Windows.Forms.Label version_current_label;
211 | private System.Windows.Forms.Label version_new_label;
212 | private System.Windows.Forms.Label version_current_value_label;
213 | private System.Windows.Forms.Label version_new_value_label;
214 | private System.Windows.Forms.TextBox changelog_textbox;
215 | private System.Windows.Forms.Label changelog_label;
216 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
217 | private System.Windows.Forms.Label filesize_label;
218 | private System.Windows.Forms.Label filesize_value_label;
219 | private System.Windows.Forms.Button show_button;
220 | private System.Windows.Forms.Label date_label;
221 | private System.Windows.Forms.Label date_value_label;
222 | private System.Windows.Forms.Button close_button;
223 | }
224 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/MainForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows.Forms;
6 | using Steamworks;
7 | using XCOM2Launcher.Classes.Steam;
8 | using XCOM2Launcher.Mod;
9 | using XCOM2Launcher.Steam;
10 | using XCOM2Launcher.XCOM;
11 |
12 | namespace XCOM2Launcher.Forms
13 | {
14 | public partial class MainForm
15 | {
16 | private const string StatusBarIdleString = "Ready.";
17 | private const string ExclamationIconKey = "Exclamation";
18 |
19 | public MainForm(Settings settings)
20 | {
21 | //
22 | InitializeComponent();
23 |
24 | // Settings
25 | SteamAPIWrapper.InitSafe();
26 | Settings = settings;
27 |
28 | // Restore states
29 | showHiddenModsToolStripMenuItem.Checked = settings.ShowHiddenElements;
30 |
31 | #if !DEBUG
32 | // hide config tab
33 | modinfo_config_tab.Parent.Controls.Remove(modinfo_config_tab);
34 | #endif
35 |
36 | // Init interface
37 | InitObjectListView();
38 | UpdateInterface();
39 | RegisterEvents();
40 |
41 | //Other intialization
42 | InitializeTabImages();
43 |
44 | // Check for Updates
45 | CheckSteamForUpdates();
46 |
47 | // Check for running downloads
48 | #if DEBUG
49 | if (Settings.GetWorkshopPath() != null)
50 | {
51 | CheckSteamForNewMods();
52 |
53 | var t = new Timer();
54 | t.Tick += (sender, e) => { CheckSteamForNewMods(); };
55 | t.Interval = 30000;
56 | t.Start();
57 | }
58 | #endif
59 | }
60 |
61 | public Settings Settings { get; set; }
62 |
63 | private void InitializeTabImages()
64 | {
65 | tabImageList.Images.Add(ExclamationIconKey, error_provider.Icon);
66 | }
67 |
68 | private void CheckSteamForNewMods()
69 | {
70 | status_toolstrip_label.Text = "Checking for new mods...";
71 |
72 | ulong[] subscribedIDs;
73 | try
74 | {
75 | subscribedIDs = Workshop.GetSubscribedItems();
76 | }
77 | catch (InvalidOperationException)
78 | {
79 | // Steamworks not initialized?
80 | // Game taking over?
81 | status_toolstrip_label.Text = "Error checking for new mods.";
82 | return;
83 | }
84 |
85 | var change = false;
86 | foreach (var id in subscribedIDs)
87 | {
88 | var status = Workshop.GetDownloadStatus(id);
89 |
90 | if (status.HasFlag(EItemState.k_EItemStateInstalled))
91 | // already installed
92 | continue;
93 |
94 | if (Downloads.Any(d => d.WorkshopID == (long) id))
95 | // already observing
96 | continue;
97 |
98 | // Get info
99 | var detailsRequest = new ItemDetailsRequest(id).Send().WaitForResult();
100 | var details = detailsRequest.Result;
101 | var link = detailsRequest.GetPreviewURL();
102 |
103 | var downloadMod = new ModEntry
104 | {
105 | Name = details.m_rgchTitle,
106 | DateCreated = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeCreated).DateTime,
107 | DateUpdated = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeUpdated).DateTime,
108 | Path = Path.Combine(Settings.GetWorkshopPath(), "" + id),
109 | Image = link,
110 | Source = ModSource.SteamWorkshop,
111 | WorkshopID = (int) id,
112 | State = ModState.New | ModState.NotInstalled
113 | };
114 |
115 | // Start download
116 | Workshop.DownloadItem(id);
117 | //
118 | Downloads.Add(downloadMod);
119 | change = true;
120 | }
121 |
122 | if (change)
123 | RefreshModList();
124 |
125 | status_toolstrip_label.Text = StatusBarIdleString;
126 | }
127 |
128 | private void CheckSteamForUpdates()
129 | {
130 | progress_toolstrip_progressbar.Value = 0;
131 | progress_toolstrip_progressbar.Maximum = Mods.All.Count();
132 | progress_toolstrip_progressbar.Visible = true;
133 | _updateWorker.RunWorkerAsync();
134 | }
135 |
136 | #region Export
137 |
138 | private void UpdateExport()
139 | {
140 | var str = new StringBuilder();
141 |
142 | if (!Mods.Active.Any())
143 | {
144 | export_richtextbox.Text = "No active mods.";
145 | return;
146 | }
147 |
148 | var nameLength = Mods.Active.Max(m => m.Name.Length);
149 | var idLength = Mods.Active.Max(m => m.ID.Length);
150 | var showCategories = export_group_checkbox.Checked;
151 |
152 | foreach (var entry in Mods.Entries.Where(e => e.Value.Entries.Any(m => m.isActive)))
153 | {
154 | var mods = entry.Value.Entries.Where(m => m.isActive).ToList();
155 |
156 | if (showCategories)
157 | str.AppendLine($"{entry.Key} ({mods.Count()}):");
158 |
159 | foreach (var mod in mods)
160 | {
161 | if (showCategories)
162 | str.Append("\t");
163 |
164 | str.Append(string.Format("{0,-" + nameLength + "} ", mod.Name));
165 | str.Append("\t");
166 | str.Append(string.Format("{0,-" + idLength + "} ", mod.ID));
167 | str.Append("\t");
168 |
169 | if (mod.WorkshopID == -1)
170 | str.Append("Unknown");
171 |
172 | else if (export_workshop_link_checkbox.Checked)
173 | str.Append(mod.GetWorkshopLink());
174 |
175 | else
176 | str.Append(mod.WorkshopID.ToString());
177 |
178 | str.AppendLine();
179 | }
180 |
181 | if (export_group_checkbox.Checked)
182 | str.AppendLine();
183 | }
184 |
185 | export_richtextbox.Text = str.ToString();
186 | }
187 |
188 | #endregion
189 |
190 | #region Basic
191 |
192 | private void Reset()
193 | {
194 | _updateWorker.CancelAsync();
195 | // let's hope it cancels fast enough...
196 |
197 | modlist_objectlistview.Clear();
198 |
199 | Settings = Program.InitializeSettings();
200 |
201 | InitObjectListView();
202 |
203 | //UpdateWorker.RunWorkerAsync();
204 | //RefreshModList();
205 | }
206 |
207 | private void Save()
208 | {
209 | XCOM2.SaveChanges(Settings);
210 | Settings.SaveFile("settings.json");
211 | }
212 |
213 | private void RunGame()
214 | {
215 | _updateWorker.CancelAsync();
216 | Save();
217 |
218 | XCOM2.RunGame(Settings.GamePath, Settings.Arguments.ToString());
219 |
220 | if (Settings.CloseAfterLaunch)
221 | Close();
222 | }
223 |
224 | #endregion
225 |
226 | #region Interface updates
227 |
228 | private void UpdateInterface()
229 | {
230 | error_provider.Clear();
231 |
232 | // Incompability warnings and overwrites grid
233 | UpdateConflicts();
234 |
235 | // ModEntry list
236 | // RefreshModList();
237 |
238 | // ModEntry details
239 | UpdateModInfo(modlist_objectlistview.SelectedObject as ModEntry);
240 |
241 | UpdateLabels();
242 | }
243 |
244 | private void UpdateLabels()
245 | {
246 | //
247 | var hasConflicts = NumConflicts > 0;
248 | modlist_tab.Text = $"Mods ({Mods.Active.Count()} / {Mods.All.Count()})";
249 | conflicts_tab.Text = "Overrides" + (hasConflicts ? $" ({NumConflicts} Conflicts)" : "");
250 | conflicts_tab.ImageKey = hasConflicts ? ExclamationIconKey : null;
251 | }
252 |
253 |
254 | public int NumConflicts;
255 |
256 | private void UpdateConflicts()
257 | {
258 | NumConflicts = 0;
259 |
260 | // Fill ClassOverride DataGrid
261 | conflicts_datagrid.Rows.Clear();
262 |
263 | foreach (var m in Mods.Active)
264 | {
265 | foreach (var classOverride in m.GetOverrides(true))
266 | {
267 | var oldClass = classOverride.OldClass;
268 |
269 | if (classOverride.OverrideType == ModClassOverrideType.UIScreenListener)
270 | oldClass += " (UIScreenListener)";
271 |
272 | conflicts_datagrid.Rows.Add(m.Name, oldClass, classOverride.NewClass);
273 | }
274 | }
275 |
276 | // Conflict log
277 | conflicts_textbox.Text = GetDuplicatesString() + GetOverridesString();
278 |
279 | // Update Interface
280 | modlist_objectlistview.UpdateObjects(ModList.Objects.ToList());
281 | UpdateLabels();
282 | }
283 |
284 | private string GetDuplicatesString()
285 | {
286 | var str = new StringBuilder();
287 |
288 | var duplicates = Mods.GetDuplicates().ToList();
289 | if (duplicates.Any())
290 | {
291 | str.AppendLine("Mods with colliding package ids found!");
292 | str.AppendLine("These can only be (de-)activated together.");
293 | str.AppendLine();
294 |
295 | foreach (var grouping in duplicates)
296 | {
297 | NumConflicts++;
298 |
299 | str.AppendLine(grouping.Key);
300 |
301 | foreach (var m in grouping)
302 | str.AppendLine($"\t{m.Name}");
303 |
304 |
305 | str.AppendLine();
306 | }
307 |
308 | str.AppendLine();
309 | }
310 | return str.ToString();
311 | }
312 |
313 | private string GetOverridesString()
314 | {
315 | var conflicts = Mods.GetActiveConflicts().ToList();
316 | if (!conflicts.Any())
317 | return "";
318 |
319 | var showUIScreenListenerMessage = false;
320 |
321 | var str = new StringBuilder();
322 |
323 | str.AppendLine("Mods with colliding overrides found!");
324 | str.AppendLine("These mods will not (fully) work when run together.");
325 | str.AppendLine();
326 |
327 | foreach (var conflict in conflicts)
328 | {
329 | str.AppendLine($"Conflict found for '{conflict.ClassName}':");
330 | var hasMultipleUIScreenListeners = conflict.Overrides.Count(o => o.OverrideType == ModClassOverrideType.UIScreenListener) > 1;
331 |
332 | foreach (var classOverride in conflict.Overrides.OrderBy(o => o.OverrideType).ThenBy(o => o.Mod.Name))
333 | {
334 | if (hasMultipleUIScreenListeners && classOverride.OverrideType == ModClassOverrideType.UIScreenListener)
335 | {
336 | showUIScreenListenerMessage = true;
337 | str.AppendLine($"\t* {classOverride.Mod.Name}");
338 | }
339 | else
340 | {
341 | str.AppendLine($"\t{classOverride.Mod.Name}");
342 | }
343 | }
344 |
345 | str.AppendLine();
346 |
347 | NumConflicts++;
348 | }
349 |
350 | error_provider.SetError(conflicts_log_label, "Found " + NumConflicts + " conflicts");
351 |
352 | if (showUIScreenListenerMessage)
353 | {
354 | str.AppendLine("* (These mods use UIScreenListeners, meaning they do not conflict with each other)");
355 | str.AppendLine();
356 | }
357 |
358 | return str.ToString();
359 | }
360 |
361 | private void UpdateModInfo(ModEntry m)
362 | {
363 | if (m == null)
364 | {
365 | // hide panel
366 | horizontal_splitcontainer.Panel2Collapsed = true;
367 | return;
368 | }
369 |
370 | // show panel
371 | horizontal_splitcontainer.Panel2Collapsed = false;
372 |
373 | // Update data
374 | modinfo_title_textbox.Text = m.Name;
375 | modinfo_author_textbox.Text = m.Author;
376 | modinfo_date_created_textbox.Text = m.DateCreated?.ToString() ?? "";
377 | modinfo_date_added_textbox.Text = m.DateAdded?.ToString() ?? "";
378 | modinfo_description_richtextbox.Text = m.GetDescription();
379 | modinfo_readme_richtextbox.Text = m.GetReadMe();
380 | modinfo_image_picturebox.ImageLocation = m.Image;
381 |
382 | modinfo_inspect_propertygrid.SelectedObject = m;
383 |
384 | #region Config
385 |
386 | // config files
387 | //string[] configFiles = m.getConfigFiles();
388 |
389 | //// clear
390 | //modinfo_config_propertygrid.SelectedObjects = new object[] { };
391 |
392 | //if (configFiles.Length > 0)
393 | //{
394 | // List configs = new List();
395 |
396 | // foreach (string configFile in configFiles)
397 | // {
398 |
399 | // ConfigFile config = new ConfigFile
400 | // {
401 | // Name = Path.GetFileName(configFile)
402 | // };
403 |
404 | // var setting = new ConfigSetting
405 | // {
406 | // Name = "Unknown",
407 | // Category = "Unknown",
408 | // Value = 100,
409 | // DefaultValue = 10,
410 | // Desc = "123"
411 | // };
412 |
413 | // config.Settings.Add(setting);
414 | // configs.Add(config);
415 | // }
416 |
417 | // modinfo_config_propertygrid.SelectedObjects = configs.ToArray
418 | //}
419 |
420 | #endregion
421 | }
422 |
423 | #endregion
424 | }
425 | }
--------------------------------------------------------------------------------
/xcom2-launcher/xcom2-launcher/Forms/MainForm.Events.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text.RegularExpressions;
8 | using System.Threading.Tasks;
9 | using System.Windows.Forms;
10 | using Steamworks;
11 | using XCOM2Launcher.Mod;
12 | using XCOM2Launcher.PropertyGrid;
13 | using XCOM2Launcher.Steam;
14 | using XCOM2Launcher.XCOM;
15 |
16 | namespace XCOM2Launcher.Forms
17 | {
18 | partial class MainForm
19 | {
20 | internal void RegisterEvents()
21 | {
22 | // Register Events
23 | // run button
24 | run_game_button.Click += (a, b) => { RunGame(); };
25 |
26 | // save on close
27 | Shown += MainForm_Shown;
28 | FormClosing += MainForm_FormClosing;
29 |
30 | // Menu
31 | // -> File
32 | saveToolStripMenuItem.Click += delegate { Save(); };
33 | reloadToolStripMenuItem.Click += delegate
34 | {
35 | // Confirmation dialog
36 | var r = MessageBox.Show("Unsaved changes will be lost.\r\nAre you sure?", "Reload mod list?", MessageBoxButtons.OKCancel);
37 | if (r != DialogResult.OK)
38 | return;
39 |
40 | Reset();
41 | };
42 | searchForModsToolStripMenuItem.Click += delegate { Settings.ImportMods(); };
43 | updateEntriesToolStripMenuItem.Click += delegate
44 | {
45 | if (_updateWorker.IsBusy)
46 | return;
47 |
48 | _updateWorker.RunWorkerAsync();
49 | };
50 | // -> Settings
51 | // show hidden
52 | showHiddenModsToolStripMenuItem.Click += delegate
53 | {
54 | Settings.ShowHiddenElements = showHiddenModsToolStripMenuItem.Checked;
55 | RefreshModList();
56 | };
57 |
58 | // Edit
59 | editSettingsToolStripMenuItem.Click += delegate
60 | {
61 | new SettingsDialog(Settings).ShowDialog();
62 | RefreshModList();
63 | };
64 |
65 | exitToolStripMenuItem.Click += (sender, e) => { Close(); };
66 |
67 | // -> Tools
68 | cleanModsToolStripMenuItem.Click += delegate { new CleanModsForm(Settings).ShowDialog(); };
69 | importActiveModsToolStripMenuItem.Click += delegate
70 | {
71 | XCOM2.ImportActiveMods(Settings);
72 | RefreshModList();
73 | };
74 |
75 | // RichTextBox clickable links
76 | modinfo_readme_richtextbox.LinkClicked += ControlLinkClicked;
77 | modinfo_description_richtextbox.LinkClicked += ControlLinkClicked;
78 | export_richtextbox.LinkClicked += ControlLinkClicked;
79 | modinfo_changelog_richtextbox.LinkClicked += ControlLinkClicked;
80 |
81 | // Tab Controls
82 | main_tabcontrol.Selected += MainTabSelected;
83 | modinfo_tabcontrol.Selected += ModInfoTabSelected;
84 |
85 | // Mod Updater
86 | _updateWorker.DoWork += Updater_DoWork;
87 | _updateWorker.ProgressChanged += Updater_ProgressChanged;
88 | _updateWorker.RunWorkerCompleted += Updater_RunWorkerCompleted;
89 |
90 | // Steam Events
91 | #if DEBUG
92 | Workshop.OnItemDownloaded += SteamWorkshop_OnItemDownloaded;
93 | #endif
94 |
95 | // Main Tabs
96 | // Export
97 | export_workshop_link_checkbox.CheckedChanged += ExportCheckboxCheckedChanged;
98 | export_group_checkbox.CheckedChanged += ExportCheckboxCheckedChanged;
99 | export_save_button.Click += ExportSaveButtonClick;
100 | export_load_button.Click += ExportLoadButtonClick;
101 | }
102 |
103 | #if DEBUG
104 | private void SteamWorkshop_OnItemDownloaded(object sender, Workshop.DownloadItemEventArgs e)
105 | {
106 | if (e.Result.m_eResult != EResult.k_EResultOK)
107 | {
108 | MessageBox.Show($"{e.Result.m_nPublishedFileId}: {e.Result.m_eResult}");
109 | return;
110 | }
111 |
112 | var m = Downloads.SingleOrDefault(x => x.WorkshopID == (long)e.Result.m_nPublishedFileId.m_PublishedFileId);
113 |
114 | if (m != null)
115 | {
116 | // look for .XComMod file
117 | var infoFile = Directory.GetFiles(m.Path, "*.XComMod", SearchOption.TopDirectoryOnly).SingleOrDefault();
118 | if (infoFile == null)
119 | throw new Exception("Invalid Download");
120 |
121 | // Fill fields
122 | m.State &= ~ModState.NotInstalled;
123 | m.ID = Path.GetFileNameWithoutExtension(infoFile);
124 | m.Image = null; // Use default image again
125 |
126 | // load info
127 | var info = new ModInfo(m.GetModInfoFile());
128 |
129 | // Move mod
130 | Downloads.Remove(m);
131 | Mods.AddMod(info.Category, m);
132 |
133 | // update listitem
134 | //var item = modlist_listview.Items.Cast().Single(i => (i.Tag as ModEntry).SourceID == m.SourceID);
135 | //UpdateModListItem(item, info.Category);
136 | }
137 | m = Mods.All.Single(x => x.WorkshopID == (long)e.Result.m_nPublishedFileId.m_PublishedFileId);
138 |
139 | MessageBox.Show($"{m.Name} finished download.");
140 | }
141 | #endif
142 |
143 | #region Form
144 |
145 | private void MainForm_Shown(object sender, EventArgs e)
146 | {
147 | if (Settings.Windows.ContainsKey("main"))
148 | {
149 | var setting = Settings.Windows["main"];
150 | DesktopBounds = setting.Bounds;
151 | WindowState = setting.State;
152 | }
153 | }
154 |
155 |
156 | private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
157 | {
158 | _updateWorker.CancelAsync();
159 |
160 | // Save dimensions
161 | Settings.Windows["main"] = new WindowSettings(this) { Data = modlist_objectlistview.SaveState() };
162 |
163 | Save();
164 | }
165 |
166 | // Make sure property grid columns are properly sized
167 | private void modinfo_inspect_propertygrid_Layout(object sender, LayoutEventArgs e)
168 | {
169 | modinfo_inspect_propertygrid.SetLabelColumnWidth(100);
170 | }
171 |
172 | #endregion
173 |
174 | #region Mod Updater
175 |
176 | private readonly BackgroundWorker _updateWorker = new BackgroundWorker
177 | {
178 | WorkerReportsProgress = true,
179 | WorkerSupportsCancellation = true
180 | };
181 |
182 | private void Updater_DoWork(object sender, DoWorkEventArgs e)
183 | {
184 | _updateWorker.ReportProgress(0);
185 | var numCompletedMods = 0;
186 | Parallel.ForEach(Mods.All.ToList(), mod =>
187 | {
188 | if (_updateWorker.CancellationPending || Disposing || IsDisposed)
189 | {
190 | e.Cancel = true;
191 | return;
192 | }
193 |
194 | Mods.UpdateMod(mod, Settings);
195 |
196 | lock (_updateWorker)
197 | {
198 | numCompletedMods++;
199 | _updateWorker.ReportProgress(numCompletedMods, mod);
200 | }
201 | });
202 | }
203 |
204 | private void Updater_ProgressChanged(object sender, ProgressChangedEventArgs e)
205 | {
206 | if (((BackgroundWorker)sender).CancellationPending)
207 | return;
208 |
209 | progress_toolstrip_progressbar.Value = e.ProgressPercentage;
210 | status_toolstrip_label.Text = $"Updating Mods... ({e.ProgressPercentage} / {progress_toolstrip_progressbar.Maximum})";
211 |
212 | // Update list item
213 | var m = e.UserState as ModEntry;
214 | if (m == null)
215 | return;
216 |
217 | UpdateMod(m);
218 | }
219 |
220 | private void Updater_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
221 | {
222 | if (e.Cancelled)
223 | {
224 | status_toolstrip_label.Text = "Cancelled.";
225 | return;
226 | }
227 |
228 | progress_toolstrip_progressbar.Visible = false;
229 | status_toolstrip_label.Text = StatusBarIdleString;
230 | RefreshModList();
231 | }
232 |
233 | #endregion
234 |
235 | #region Event Handlers
236 | private void MainTabSelected(object sender, TabControlEventArgs e)
237 | {
238 | if (e.TabPage == export_tab)
239 | UpdateExport();
240 | }
241 |
242 | private void ExportCheckboxCheckedChanged(object sender, EventArgs e)
243 | {
244 | UpdateExport();
245 | }
246 | private void ExportLoadButtonClick(object sender, EventArgs e)
247 | {
248 | var dialog = new OpenFileDialog
249 | {
250 | Filter = "Text files|*.txt",
251 | DefaultExt = "txt",
252 | CheckPathExists = true,
253 | CheckFileExists = true,
254 | Multiselect = false,
255 | };
256 |
257 | if (dialog.ShowDialog() != DialogResult.OK)
258 | return;
259 |
260 | // parse file
261 |
262 | var regex = new Regex(@"^\s*(?.*?)[ ]*\t(?.*?)[ ]*\t(?:.*=)?(?\d+)$", RegexOptions.Compiled | RegexOptions.Multiline);
263 |
264 | var mods = Mods.All.ToList();
265 | var activeMods = new List();
266 | var missingMods = new List();
267 |
268 | foreach (var line in File.ReadAllLines(dialog.FileName))
269 | {
270 | var match = regex.Match(line);
271 | if (!match.Success)
272 | continue;
273 |
274 | var entries = mods.Where(mod => mod.ID == match.Groups["id"].Value).ToList();
275 |
276 | if (entries.Count == 0)
277 | {
278 | // Mod missing
279 | // -> add to list
280 | missingMods.Add(match);
281 | continue;
282 | }
283 |
284 | activeMods.AddRange(entries);
285 |
286 | if (entries.Count > 1)
287 | {
288 | // More than 1 mod
289 | // Add warning?
290 | }
291 | }
292 |
293 | // Check entries
294 | if (activeMods.Count == 0)
295 | {
296 | MessageBox.Show("No mods found. Bad profile?");
297 | return;
298 | }
299 |
300 | // Check missing
301 | if (missingMods.Count > 0)
302 | {
303 | var steamMissingMods = missingMods.Where(match => match.Groups["sourceID"].Value != "Unknown").ToList();
304 |
305 | var text = $"This profile contains {missingMods.Count} mod(s) that are not currently installed:\r\n\r\n";
306 |
307 | foreach (var match in missingMods)
308 | {
309 | text += match.Groups["name"].Value;
310 |
311 | if (steamMissingMods.Contains(match))
312 | text += "*";
313 |
314 | text += "\r\n";
315 | }
316 |
317 | if (steamMissingMods.Count != 0)
318 | {
319 | text += "\r\nDo you want to subscribe to the mods marked with an asterisk on Steam?";
320 |
321 | var result = MessageBox.Show(this, text, "Mods missing!", MessageBoxButtons.YesNoCancel);
322 |
323 | if (result == DialogResult.Cancel)
324 | return;
325 |
326 | if (result == DialogResult.Yes)
327 | {
328 | // subscribe
329 | foreach (var id in steamMissingMods.Select(match => ulong.Parse(match.Groups["sourceID"].Value)))
330 | {
331 | SteamUGC.SubscribeItem(id.ToPublishedFileID());
332 | }
333 |
334 | MessageBox.Show("Done. Close the launcher, wait for steam to download the mod(s) and try again.");
335 | return;
336 | }
337 | }
338 | else
339 | {
340 | text += "\r\nDo you wish to continue?";
341 |
342 | if (MessageBox.Show(this, text, "Mods missing!", MessageBoxButtons.YesNo) == DialogResult.No)
343 | return;
344 | }
345 | }
346 |
347 | // Confirm
348 | if (MessageBox.Show(this, $"Adopt profile? {activeMods.Count} mods found.", "Confirm", MessageBoxButtons.YesNo) == DialogResult.No)
349 | return;
350 |
351 | // Apply changes
352 | foreach (var mod in mods)
353 | mod.isActive = false;
354 |
355 | foreach (var mod in activeMods)
356 | mod.isActive = true;
357 |
358 | modlist_objectlistview.UpdateObjects(mods);
359 |
360 | UpdateExport();
361 | UpdateLabels();
362 | }
363 |
364 | private void ExportSaveButtonClick(object sender, EventArgs eventArgs)
365 | {
366 | var dialog = new SaveFileDialog
367 | {
368 | Filter = "Text files|*.txt",
369 | DefaultExt = "txt",
370 | OverwritePrompt = true,
371 | AddExtension = true,
372 | };
373 |
374 | if (dialog.ShowDialog() != DialogResult.OK)
375 | return;
376 |
377 | File.WriteAllText(dialog.FileName, export_richtextbox.Text);
378 | }
379 |
380 | private void ModInfoTabSelected(object sender, TabControlEventArgs e)
381 | {
382 | CheckAndUpdateChangeLog(e.TabPage, modlist_objectlistview.SelectedObject as ModEntry);
383 | }
384 |
385 | private async void CheckAndUpdateChangeLog(TabPage tab, ModEntry m)
386 | {
387 | if (tab != modinfo_changelog_tab || m == null)
388 | return;
389 |
390 | modinfo_changelog_richtextbox.Text = "Loading...";
391 | modinfo_changelog_richtextbox.Text = await ModChangelogCache.GetChangeLogAsync(m.WorkshopID);
392 | }
393 |
394 | private void ControlLinkClicked(object sender, LinkClickedEventArgs e)
395 | {
396 | Process.Start(e.LinkText);
397 | }
398 | #endregion
399 | }
400 | }
--------------------------------------------------------------------------------