├── xcom2-launcher ├── xcom2-launcher │ ├── steam_appid.txt │ ├── GitVersionConfig.yaml │ ├── CSteamworks.dll │ ├── steam_api64.dll │ ├── Resources │ │ └── xcom.ico │ ├── Steamworks.NET.dll │ ├── Classes │ │ ├── Mod │ │ │ ├── ModSource.cs │ │ │ ├── ModCategory.cs │ │ │ ├── ModState.cs │ │ │ ├── ModConflict.cs │ │ │ ├── ModClassOverride.cs │ │ │ ├── ModChangelogCache.cs │ │ │ ├── ModInfo.cs │ │ │ ├── ModEntry.cs │ │ │ └── ModList.cs │ │ ├── WindowSetting.cs │ │ ├── Steam │ │ │ ├── DownloadItemRequest.cs │ │ │ ├── SteamWorkshop.cs │ │ │ ├── SteamAPIWrapper.cs │ │ │ ├── ItemDetailsRequest.cs │ │ │ ├── Workshop.cs │ │ │ └── SteamManager.cs │ │ ├── GitHub │ │ │ ├── Asset.cs │ │ │ ├── Release.cs │ │ │ └── User.cs │ │ ├── PropertyGrid │ │ │ ├── PropertyGridExtensions.cs │ │ │ ├── PropertyCheckbox.cs │ │ │ └── DictionaryPropertyGridAdapter.cs │ │ ├── Helper │ │ │ └── FileSizeFormatExtension.cs │ │ ├── Serialization │ │ │ └── ModListConverter.cs │ │ ├── Settings.cs │ │ └── XCOM │ │ │ ├── Arguments.cs │ │ │ ├── old │ │ │ └── Config.cs │ │ │ ├── ConfigFile.cs │ │ │ ├── XCOM2.cs │ │ │ └── IniFile.cs │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── packages.config │ ├── app.config │ ├── Forms │ │ ├── UpdateAvailableDialog.cs │ │ ├── CleanModsForm.cs │ │ ├── SettingsDialog.resx │ │ ├── UpdateAvailableDialog.resx │ │ ├── CleanModsForm.resx │ │ ├── MainForm.resx │ │ ├── SettingsDialog.cs │ │ ├── CleanModsForm.Designer.cs │ │ ├── UpdateAvailableDialog.Designer.cs │ │ ├── MainForm.cs │ │ └── MainForm.Events.cs │ ├── UserElements │ │ └── AutoCompleteTextBox.cs │ ├── Program.cs │ └── xcom2-launcher.csproj └── xcom2-launcher.sln ├── readme.md ├── .gitattributes └── .gitignore /xcom2-launcher/xcom2-launcher/steam_appid.txt: -------------------------------------------------------------------------------- 1 | 268500 -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/GitVersionConfig.yaml: -------------------------------------------------------------------------------- 1 | assembly-versioning-scheme: MajorMinorPatch 2 | next-version: 0.3.0 -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/CSteamworks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aEnigmatic/xcom2-launcher/HEAD/xcom2-launcher/xcom2-launcher/CSteamworks.dll -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/steam_api64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aEnigmatic/xcom2-launcher/HEAD/xcom2-launcher/xcom2-launcher/steam_api64.dll -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Resources/xcom.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aEnigmatic/xcom2-launcher/HEAD/xcom2-launcher/xcom2-launcher/Resources/xcom.ico -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Steamworks.NET.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aEnigmatic/xcom2-launcher/HEAD/xcom2-launcher/xcom2-launcher/Steamworks.NET.dll -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModSource.cs: -------------------------------------------------------------------------------- 1 | namespace XCOM2Launcher.Mod 2 | { 3 | public enum ModSource 4 | { 5 | Unknown, 6 | SteamWorkshop, 7 | Nexus, 8 | ModBuddy, 9 | Manual 10 | } 11 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModCategory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XCOM2Launcher.Mod 4 | { 5 | public class ModCategory 6 | { 7 | public int Index { get; set; } = -1; 8 | 9 | public bool Collapsed { get; set; } = false; 10 | 11 | public List Entries { get; set; } = new List(); 12 | } 13 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XCOM2Launcher.Mod 4 | { 5 | [Flags] 6 | public enum ModState 7 | { 8 | None = 0, 9 | New = 1, 10 | UpdateAvailable = 2, 11 | 12 | 13 | ModConflict = 8, 14 | DuplicateID = 16, 15 | NotLoaded = 32, 16 | NotInstalled = 64, 17 | } 18 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModConflict.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XCOM2Launcher.Mod 4 | { 5 | public class ModConflict 6 | { 7 | public string ClassName { get; private set; } 8 | public IEnumerable Overrides { get; private set; } 9 | 10 | public ModConflict(string className, IEnumerable overrides) 11 | { 12 | ClassName = className; 13 | Overrides = overrides; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/WindowSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Forms; 3 | 4 | namespace XCOM2Launcher 5 | { 6 | public class WindowSettings 7 | { 8 | public WindowSettings() 9 | { 10 | } 11 | 12 | public WindowSettings(Form form) 13 | { 14 | State = form.WindowState; 15 | 16 | // Restore state to get normal bounds 17 | if (form.WindowState != FormWindowState.Normal) 18 | form.WindowState = FormWindowState.Normal; 19 | 20 | Bounds = form.DesktopBounds; 21 | } 22 | 23 | public Rectangle Bounds { get; set; } 24 | public FormWindowState State { get; set; } 25 | public byte[] Data { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModClassOverride.cs: -------------------------------------------------------------------------------- 1 | namespace XCOM2Launcher.Mod 2 | { 3 | public class ModClassOverride 4 | { 5 | public ModEntry Mod { get; private set; } 6 | public string NewClass { get; private set; } 7 | public string OldClass { get; private set; } 8 | public ModClassOverrideType OverrideType { get; private set; } 9 | 10 | public ModClassOverride(ModEntry mod, string newClass, string oldClass, ModClassOverrideType overrideType) 11 | { 12 | Mod = mod; 13 | NewClass = newClass; 14 | OldClass = oldClass; 15 | OverrideType = overrideType; 16 | } 17 | } 18 | 19 | public enum ModClassOverrideType 20 | { 21 | Class, 22 | UIScreenListener 23 | } 24 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Steam/DownloadItemRequest.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using XCOM2Launcher.Classes.Steam; 3 | 4 | namespace XCOM2Launcher.Steam 5 | { 6 | public class DownloadItemRequest 7 | { 8 | // ReSharper disable once NotAccessedField.Local 9 | private readonly Callback _callback; 10 | private readonly ulong _id; 11 | 12 | public DownloadItemRequest(ulong id, Callback.DispatchDelegate callback) 13 | { 14 | _id = id; 15 | _callback = Callback.Create(callback); 16 | } 17 | 18 | public void Send() 19 | { 20 | SteamAPIWrapper.Init(); 21 | SteamUGC.DownloadItem(new PublishedFileId_t(_id), true); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/GitHub/Asset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace XCOM2Launcher.GitHub 8 | { 9 | public class Asset 10 | { 11 | public string url {get;set;} 12 | public string id {get;set;} 13 | public string name {get;set;} 14 | public string label {get;set;} 15 | public User uploader {get;set;} 16 | public string content_type {get;set;} 17 | public string state {get;set;} 18 | public int size {get;set;} 19 | public int download_count {get;set;} 20 | public DateTime created_at {get;set;} 21 | public DateTime updated_at {get;set;} 22 | public string browser_download_url {get;set;} 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/PropertyGrid/PropertyGridExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows.Forms; 3 | 4 | namespace XCOM2Launcher.PropertyGrid 5 | { 6 | static class PropertyGridExtensions 7 | { 8 | /// 9 | /// Resize the columns of a PropertyGrid. From http://stackoverflow.com/questions/12447156/14475276#14475276 10 | /// 11 | public static void SetLabelColumnWidth(this System.Windows.Forms.PropertyGrid grid, int width) 12 | { 13 | FieldInfo fi = grid?.GetType().GetField("gridView", BindingFlags.Instance | BindingFlags.NonPublic); 14 | Control view = fi?.GetValue(grid) as Control; 15 | MethodInfo mi = view?.GetType().GetMethod("MoveSplitterTo", BindingFlags.Instance | BindingFlags.NonPublic); 16 | 17 | mi?.Invoke(view, new object[] { width }); 18 | view?.Invalidate(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Steam/SteamWorkshop.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System.Collections.Generic; 3 | using System; 4 | 5 | namespace XCOM2Launcher.Steam 6 | { 7 | public static class StaticExtension 8 | { 9 | public static PublishedFileId_t ToPublishedFileID(this ulong id) => new PublishedFileId_t(id); 10 | } 11 | 12 | 13 | public class UpdateInfo 14 | { 15 | public ulong ItemID { get; set; } 16 | public ulong BytesProcessed { get; set; } 17 | public ulong BytesTotal { get; set; } 18 | public double Process 19 | { 20 | get 21 | { 22 | if (BytesTotal == 0) 23 | return double.NaN; 24 | 25 | return BytesProcessed / BytesTotal; 26 | } 27 | } 28 | } 29 | 30 | public class InstallInfo 31 | { 32 | public ulong ItemID { get; set; } 33 | public ulong SizeOnDisk { get; set; } 34 | public string Folder { get; set; } 35 | public DateTime TimeStamp { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/GitHub/Release.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace XCOM2Launcher.GitHub 8 | { 9 | public class Release 10 | { 11 | public string url { get; set; } 12 | public string assets_url { get; set; } 13 | public string upload_url { get; set; } 14 | public string html_url { get; set; } 15 | public string id { get; set; } 16 | public string tag_name { get; set; } 17 | public string target_commitish { get; set; } 18 | public string name { get; set; } 19 | public string draft { get; set; } 20 | public User author { get; set; } 21 | public string prerelease { get; set; } 22 | public DateTime created_at { get; set; } 23 | public DateTime published_at { get; set; } 24 | public List assets { get; set; } 25 | public string tarball_url { get; set; } 26 | public string zipball_url { get; set; } 27 | public string body { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/PropertyGrid/PropertyCheckbox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Drawing.Design; 4 | using System.Windows.Forms; 5 | 6 | namespace XCOM2Launcher.PropertyGrid 7 | { 8 | class CheckboxEditor : UITypeEditor 9 | { 10 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) 11 | { 12 | 13 | return UITypeEditorEditStyle.Modal; 14 | } 15 | 16 | 17 | public override bool GetPaintValueSupported(ITypeDescriptorContext context) 18 | { 19 | 20 | return true; 21 | } 22 | 23 | 24 | public override void PaintValue(PaintValueEventArgs e) 25 | { 26 | 27 | ButtonState State; 28 | 29 | bool res = Convert.ToBoolean((e.Value)); 30 | 31 | if (res) 32 | State = ButtonState.Checked; 33 | 34 | else 35 | State = ButtonState.Normal; 36 | 37 | ControlPaint.DrawCheckBox(e.Graphics, e.Bounds, State); 38 | e.Graphics.ExcludeClip(e.Bounds); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/GitHub/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace XCOM2Launcher.GitHub 8 | { 9 | public class User 10 | { 11 | public string login { get; set; } 12 | public int id { get; set; } 13 | public string avatar_url { get; set; } 14 | public string gravatar_id { get; set; } 15 | public string url { get; set; } 16 | public string html_url { get; set; } 17 | public string followers_url { get; set; } 18 | public string following_url { get; set; } 19 | public string gists_url { get; set; } 20 | public string starred_url { get; set; } 21 | public string subscriptions_url { get; set; } 22 | public string organizations_url { get; set; } 23 | public string repos_url { get; set; } 24 | public string events_url { get; set; } 25 | public string received_events_url { get; set; } 26 | public string type { get; set; } 27 | public bool site_admin { get; set; } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Helper/FileSizeFormatExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | 4 | namespace XCOM2Launcher.Helper 5 | { 6 | public static class FileSizeFormatExtension 7 | { 8 | [DllImport("Shlwapi.dll", CharSet = CharSet.Auto)] 9 | private static extern long StrFormatByteSize(long fileSize, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer, int bufferSize); 10 | 11 | 12 | /// 13 | /// 14 | /// Converts a numeric value into a string that represents the number expressed as a size value in bytes, kilobytes, 15 | /// megabytes, or gigabytes, depending on the size. 16 | /// 17 | /// The numeric value to be converted. 18 | /// the converted string 19 | public static string FormatAsFileSize(this long filesize) 20 | { 21 | var sb = new StringBuilder(11); 22 | StrFormatByteSize(filesize, sb, sb.Capacity); 23 | return sb.ToString(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Properties/Settings.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 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This is an alternative launcher / mod manager for the game XCOM2. 2 | 3 | # Links 4 | [Latest Release](https://github.com/aEnigmatic/xcom2-launcher/releases/latest) 5 | [Screenshots](http://imgur.com/a/wcpey) 6 | 7 | 8 | # Features 9 | 10 | * Skips the official xcom launcher 11 | * Categories 12 | * Basic compability check (duplicate ids, class conflicts) 13 | * Basic steam support (details, changelog, unsubscribe) 14 | * Cleans old ModOverride entries from XComEngine.ini 15 | * Can delete unnecessary files to reduce memory footprint 16 | 17 | # Requirements 18 | * [.NET 4.6](https://www.microsoft.com/de-de/download/details.aspx?id=49981) (if you're on win 7, you might need to update) 19 | * 64-bit Windows (Mac might be possible if you compile with mono) 20 | * Steam running 21 | 22 | #Setup 23 | Extract all files where-ever you want and run the exe. The launcher should detect game path etc. automatically. 24 | You can use Tools > Import active mods for a quicker first time setup, if you want. 25 | 26 | # Licence 27 | Released under GPL, due to [objectlistview](http://objectlistview.sourceforge.net/cs/index.html). 28 | 29 | # Bug reports 30 | If you encounter a bug, please open a ticket. 31 | 32 | # Credit 33 | The XCOM2 icon is property of Firaxis. -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Steam/SteamAPIWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Steamworks; 7 | 8 | namespace XCOM2Launcher.Classes.Steam 9 | { 10 | /// 11 | /// A hacky attempt to avoid race conditions caused by accessing the native Steam API from multiple threads by serializing access to it 12 | /// 13 | public static class SteamAPIWrapper 14 | { 15 | private static readonly object Mutex = new object(); 16 | 17 | public static void RunCallbacks() 18 | { 19 | lock (Mutex) 20 | { 21 | SteamAPI.RunCallbacks(); 22 | } 23 | } 24 | 25 | public static void Shutdown() 26 | { 27 | lock (Mutex) 28 | { 29 | SteamAPI.Shutdown(); 30 | } 31 | } 32 | 33 | public static bool Init() 34 | { 35 | lock (Mutex) 36 | { 37 | return SteamAPI.Init(); 38 | } 39 | } 40 | 41 | public static bool InitSafe() 42 | { 43 | lock (Mutex) 44 | { 45 | return SteamAPI.InitSafe(); 46 | } 47 | } 48 | 49 | public static bool RestartAppIfNecessary(AppId_t unOwnAppID) 50 | { 51 | lock (Mutex) 52 | { 53 | return SteamAPI.RestartAppIfNecessary(unOwnAppID); 54 | } 55 | } 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Forms/UpdateAvailableDialog.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | using XCOM2Launcher.GitHub; 7 | 8 | namespace XCOM2Launcher.Forms 9 | { 10 | public partial class UpdateAvailableDialog : Form 11 | { 12 | public UpdateAvailableDialog(Release release, string currentVersion) 13 | { 14 | InitializeComponent(); 15 | CurrentVersion = currentVersion; 16 | Release = release; 17 | 18 | UpdateLabels(); 19 | } 20 | 21 | public string CurrentVersion { get; } 22 | 23 | public Release Release { get; } 24 | 25 | private void UpdateLabels() 26 | { 27 | version_current_value_label.Text = CurrentVersion; 28 | version_new_value_label.Text = Release.tag_name; 29 | changelog_textbox.Text = Release.body; 30 | date_value_label.Text = Release.published_at.ToString(CultureInfo.CurrentCulture); 31 | 32 | 33 | var asset = Release.assets.FirstOrDefault(a => a.name.EndsWith(".zip")); 34 | 35 | filesize_value_label.Text = asset == null ? "No download available yet." : Helper.FileSizeFormatExtension.FormatAsFileSize(asset.size); 36 | 37 | } 38 | 39 | private void close_button_Click(object sender, System.EventArgs e) 40 | { 41 | Close(); 42 | } 43 | 44 | private void show_button_Click(object sender, System.EventArgs e) 45 | { 46 | Process.Start(Release.html_url); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die mit einer Assembly verknüpft sind. 8 | [assembly: AssemblyTitle("XCOM2 Launcher")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("XCOM2 Launcher")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 18 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("943d3600-eb84-4ee5-aed6-74ad9e4dd978")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 33 | // übernehmen, indem Sie "*" eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | // 36 | // Now handled by GitVersion 37 | //[assembly: AssemblyVersion("1.0.0.0")] 38 | //[assembly: AssemblyFileVersion("1.0.0.0")] 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | *.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary -------------------------------------------------------------------------------- /xcom2-launcher/xcom2-launcher/Classes/Mod/ModChangelogCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | 8 | namespace XCOM2Launcher.Mod 9 | { 10 | public static class ModChangelogCache 11 | { 12 | private static readonly Dictionary Cache = new Dictionary(); 13 | 14 | private static readonly Regex Regexp = new Regex(@"
\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 | } --------------------------------------------------------------------------------