diff --git a/CreamInstaller/Classes/ProgramSelection.cs b/CreamInstaller/Classes/ProgramSelection.cs index 0e27da5..0d13191 100644 --- a/CreamInstaller/Classes/ProgramSelection.cs +++ b/CreamInstaller/Classes/ProgramSelection.cs @@ -33,9 +33,13 @@ internal class ProgramSelection { foreach (string directory in SteamApiDllDirectories) { - string api = directory + @"\steam_api.dll"; - string api64 = directory + @"\steam_api64.dll"; - if (api.IsFilePathLocked() || api64.IsFilePathLocked()) return true; + directory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + if (api.IsFilePathLocked() + || api_o.IsFilePathLocked() + || api64.IsFilePathLocked() + || api64_o.IsFilePathLocked() + || cApi.IsFilePathLocked()) + return true; } return false; } diff --git a/CreamInstaller/Forms/DialogForm.cs b/CreamInstaller/Forms/DialogForm.cs index 825d550..3b569b7 100644 --- a/CreamInstaller/Forms/DialogForm.cs +++ b/CreamInstaller/Forms/DialogForm.cs @@ -9,8 +9,10 @@ internal partial class DialogForm : CustomForm { internal DialogForm(IWin32Window owner) : base(owner) => InitializeComponent(); - internal DialogResult Show(string formName, Icon descriptionIcon, string descriptionText, string acceptButtonText, string cancelButtonText = null) + internal DialogResult Show(string formName, Icon descriptionIcon, string descriptionText, string acceptButtonText, string cancelButtonText = null, Icon customFormIcon = null) { + if (customFormIcon is not null) + Icon = customFormIcon; icon.Image = descriptionIcon.ToBitmap(); Text = formName; descriptionLabel.Text = descriptionText; diff --git a/CreamInstaller/Forms/InstallForm.cs b/CreamInstaller/Forms/InstallForm.cs index f56b29a..f28e224 100644 --- a/CreamInstaller/Forms/InstallForm.cs +++ b/CreamInstaller/Forms/InstallForm.cs @@ -4,7 +4,6 @@ using System.Drawing; using System.IO; using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -33,23 +32,29 @@ internal partial class InstallForm : CustomForm internal void UpdateProgress(int progress) { - int value = (int)((float)(CompleteOperationsCount / (float)OperationsCount) * 100) + progress / OperationsCount; - if (value < userProgressBar.Value) return; - userProgressBar.Value = value; + if (!userProgressBar.Disposing && !userProgressBar.IsDisposed) + userProgressBar.Invoke(() => + { + int value = (int)((float)(CompleteOperationsCount / (float)OperationsCount) * 100) + progress / OperationsCount; + if (value < userProgressBar.Value) return; + userProgressBar.Value = value; + }); } - internal async Task UpdateUser(string text, Color color, bool info = true, bool log = true) + internal void UpdateUser(string text, Color color, bool info = true, bool log = true) { - if (info) userInfoLabel.Text = text; - if (log && !logTextBox.IsDisposed) + if (info) userInfoLabel.Invoke(() => userInfoLabel.Text = text); + if (log && !logTextBox.Disposing && !logTextBox.IsDisposed) { - if (logTextBox.Text.Length > 0) logTextBox.AppendText(Environment.NewLine, color); - logTextBox.AppendText(text, color); + logTextBox.Invoke(() => + { + if (logTextBox.Text.Length > 0) logTextBox.AppendText(Environment.NewLine, color); + logTextBox.AppendText(text, color); + }); } - await Task.Run(() => Thread.Sleep(0)); // to keep the text box control from glitching } - internal async Task WriteConfiguration(StreamWriter writer, int steamAppId, string name, SortedList steamDlcApps) + internal static void WriteConfiguration(StreamWriter writer, int steamAppId, string name, SortedList steamDlcApps, InstallForm installForm = null) { writer.WriteLine(); writer.WriteLine($"; {name}"); @@ -57,16 +62,93 @@ internal partial class InstallForm : CustomForm writer.WriteLine($"appid = {steamAppId}"); writer.WriteLine(); writer.WriteLine("[dlc]"); - await UpdateUser($"Added game to cream_api.ini with appid {steamAppId} ({name})", InstallationLog.Resource, info: false); + if (installForm is not null) + installForm.UpdateUser($"Added game to cream_api.ini with appid {steamAppId} ({name})", InstallationLog.Resource, info: false); foreach (KeyValuePair pair in steamDlcApps) { int appId = pair.Key; (string name, string iconStaticId) dlcApp = pair.Value; writer.WriteLine($"{appId} = {dlcApp.name}"); - await UpdateUser($"Added DLC to cream_api.ini with appid {appId} ({dlcApp.name})", InstallationLog.Resource, info: false); + if (installForm is not null) + installForm.UpdateUser($"Added DLC to cream_api.ini with appid {appId} ({dlcApp.name})", InstallationLog.Resource, info: false); } } + internal static async Task UninstallCreamAPI(string directory, InstallForm installForm = null) => await Task.Run(() => + { + directory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + if (File.Exists(api_o)) + { + if (File.Exists(api)) + { + File.Delete(api); + if (installForm is not null) + installForm.UpdateUser($"Deleted file: {Path.GetFileName(api)}", InstallationLog.Resource, info: false); + } + File.Move(api_o, api); + if (installForm is not null) + installForm.UpdateUser($"Renamed file: {Path.GetFileName(api_o)} -> {Path.GetFileName(api)}", InstallationLog.Resource, info: false); + } + if (File.Exists(api64_o)) + { + if (File.Exists(api64)) + { + File.Delete(api64); + if (installForm is not null) + installForm.UpdateUser($"Deleted file: {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); + } + File.Move(api64_o, api64); + if (installForm is not null) + installForm.UpdateUser($"Renamed file: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); + } + if (File.Exists(cApi)) + { + File.Delete(cApi); + if (installForm is not null) + installForm.UpdateUser($"Deleted file: {Path.GetFileName(cApi)}", InstallationLog.Resource, info: false); + } + }); + + internal static async Task InstallCreamAPI(string directory, ProgramSelection selection, InstallForm installForm = null) => await Task.Run(() => + { + directory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + if (File.Exists(api) && !File.Exists(api_o)) + { + File.Move(api, api_o); + if (installForm is not null) + installForm.UpdateUser($"Renamed file: {Path.GetFileName(api)} -> {Path.GetFileName(api_o)}", InstallationLog.Resource, info: false); + } + if (File.Exists(api_o)) + { + Properties.Resources.API.Write(api); + if (installForm is not null) + installForm.UpdateUser($"Wrote resource to file: {Path.GetFileName(api)}", InstallationLog.Resource, info: false); + } + if (File.Exists(api64) && !File.Exists(api64_o)) + { + File.Move(api64, api64_o); + if (installForm is not null) + installForm.UpdateUser($"Renamed file: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", InstallationLog.Resource, info: false); + } + if (File.Exists(api64_o)) + { + Properties.Resources.API64.Write(api64); + if (installForm is not null) + installForm.UpdateUser($"Wrote resource to file: {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); + } + if (installForm is not null) + installForm.UpdateUser("Generating CreamAPI for " + selection.Name + $" in directory \"{directory}\" . . . ", InstallationLog.Operation); + File.Create(cApi).Close(); + StreamWriter writer = new(cApi, true, Encoding.UTF8); + writer.WriteLine("; " + Application.CompanyName + " v" + Application.ProductVersion); + if (selection.SteamAppId > 0) + WriteConfiguration(writer, selection.SteamAppId, selection.Name, selection.SelectedSteamDlc, installForm); + foreach (Tuple> extraAppDlc in selection.ExtraSteamAppIdDlc) + WriteConfiguration(writer, extraAppDlc.Item1, extraAppDlc.Item2, extraAppDlc.Item3, installForm); + writer.Flush(); + writer.Close(); + }); + private async Task OperateFor(ProgramSelection selection) { UpdateProgress(0); @@ -74,73 +156,12 @@ internal partial class InstallForm : CustomForm int cur = 0; foreach (string directory in selection.SteamApiDllDirectories) { - await UpdateUser($"{(Uninstalling ? "Uninstalling" : "Installing")} CreamAPI for " + selection.Name + $" in directory \"{directory}\" . . . ", InstallationLog.Operation); + UpdateUser($"{(Uninstalling ? "Uninstalling" : "Installing")} CreamAPI for " + selection.Name + $" in directory \"{directory}\" . . . ", InstallationLog.Operation); if (!Program.IsProgramRunningDialog(this, selection)) throw new OperationCanceledException(); - string api = directory + @"\steam_api.dll"; - string api_o = directory + @"\steam_api_o.dll"; - string api64 = directory + @"\steam_api64.dll"; - string api64_o = directory + @"\steam_api64_o.dll"; - string cApi = directory + @"\cream_api.ini"; if (Uninstalling) - { - if (File.Exists(api_o)) - { - if (File.Exists(api)) - { - File.Delete(api); - await UpdateUser($"Deleted file: {Path.GetFileName(api)}", InstallationLog.Resource, info: false); - } - File.Move(api_o, api); - await UpdateUser($"Renamed file: {Path.GetFileName(api_o)} -> {Path.GetFileName(api)}", InstallationLog.Resource, info: false); - } - if (File.Exists(api64_o)) - { - if (File.Exists(api64)) - { - File.Delete(api64); - await UpdateUser($"Deleted file: {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); - } - File.Move(api64_o, api64); - await UpdateUser($"Renamed file: {Path.GetFileName(api64_o)} -> {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); - } - if (File.Exists(cApi)) - { - File.Delete(cApi); - await UpdateUser($"Deleted file: {Path.GetFileName(cApi)}", InstallationLog.Resource, info: false); - } - } + await UninstallCreamAPI(directory, this); else - { - if (File.Exists(api) && !File.Exists(api_o)) - { - File.Move(api, api_o); - await UpdateUser($"Renamed file: {Path.GetFileName(api)} -> {Path.GetFileName(api_o)}", InstallationLog.Resource, info: false); - } - if (File.Exists(api_o)) - { - Properties.Resources.API.Write(api); - await UpdateUser($"Wrote resource to file: {Path.GetFileName(api)}", InstallationLog.Resource, info: false); - } - if (File.Exists(api64) && !File.Exists(api64_o)) - { - File.Move(api64, api64_o); - await UpdateUser($"Renamed file: {Path.GetFileName(api64)} -> {Path.GetFileName(api64_o)}", InstallationLog.Resource, info: false); - } - if (File.Exists(api64_o)) - { - Properties.Resources.API64.Write(api64); - await UpdateUser($"Wrote resource to file: {Path.GetFileName(api64)}", InstallationLog.Resource, info: false); - } - await UpdateUser("Generating CreamAPI for " + selection.Name + $" in directory \"{directory}\" . . . ", InstallationLog.Operation); - File.Create(cApi).Close(); - StreamWriter writer = new(cApi, true, Encoding.UTF8); - writer.WriteLine("; " + Application.CompanyName + " v" + Application.ProductVersion); - if (selection.SteamAppId > 0) await WriteConfiguration(writer, selection.SteamAppId, selection.Name, selection.SelectedSteamDlc); - foreach (Tuple> extraAppDlc in selection.ExtraSteamAppIdDlc) - await WriteConfiguration(writer, extraAppDlc.Item1, extraAppDlc.Item2, extraAppDlc.Item3); - writer.Flush(); - writer.Close(); - } + await InstallCreamAPI(directory, selection, this); UpdateProgress(++cur / count * 100); } UpdateProgress(100); @@ -158,13 +179,13 @@ internal partial class InstallForm : CustomForm try { await OperateFor(selection); - await UpdateUser($"Operation succeeded for {selection.Name}.", InstallationLog.Success); + UpdateUser($"Operation succeeded for {selection.Name}.", InstallationLog.Success); selection.Enabled = false; disabledSelections.Add(selection); } catch (Exception exception) { - await UpdateUser($"Operation failed for {selection.Name}: " + exception.ToString(), InstallationLog.Error); + UpdateUser($"Operation failed for {selection.Name}: " + exception.ToString(), InstallationLog.Error); } ++CompleteOperationsCount; } @@ -188,11 +209,11 @@ internal partial class InstallForm : CustomForm try { await Operate(); - await UpdateUser($"CreamAPI successfully {(Uninstalling ? "uninstalled" : "installed and generated")} for " + ProgramCount + " program(s).", InstallationLog.Success); + UpdateUser($"CreamAPI successfully {(Uninstalling ? "uninstalled" : "installed and generated")} for " + ProgramCount + " program(s).", InstallationLog.Success); } catch (Exception exception) { - await UpdateUser($"CreamAPI {(Uninstalling ? "uninstallation" : "installation and/or generation")} failed: " + exception.ToString(), InstallationLog.Error); + UpdateUser($"CreamAPI {(Uninstalling ? "uninstallation" : "installation and/or generation")} failed: " + exception.ToString(), InstallationLog.Error); retryButton.Enabled = true; } userProgressBar.Value = userProgressBar.Maximum; diff --git a/CreamInstaller/Forms/SelectForm.cs b/CreamInstaller/Forms/SelectForm.cs index 7b78b0b..2b18412 100644 --- a/CreamInstaller/Forms/SelectForm.cs +++ b/CreamInstaller/Forms/SelectForm.cs @@ -12,6 +12,7 @@ using System.Windows.Forms; using CreamInstaller.Classes; using CreamInstaller.Forms.Components; +using CreamInstaller.Resources; using Gameloop.Vdf.Linq; @@ -58,9 +59,13 @@ internal partial class SelectForm : CustomForm { List dllDirectories = new(); if (Program.Canceled || !Directory.Exists(gameDirectory)) return null; - string api = gameDirectory + @"\steam_api.dll"; - string api64 = gameDirectory + @"\steam_api64.dll"; - if (File.Exists(api) || File.Exists(api64)) dllDirectories.Add(gameDirectory); + gameDirectory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + if (File.Exists(api) + || File.Exists(api_o) + || File.Exists(api64) + || File.Exists(api64_o) + || File.Exists(cApi)) + dllDirectories.Add(gameDirectory); string[] directories = Directory.GetDirectories(gameDirectory); foreach (string _directory in directories) { @@ -549,6 +554,56 @@ internal partial class SelectForm : CustomForm } if (selection is not null) { + if (appId == 0) + { + nodeContextMenu.Items.Add(new ToolStripSeparator()); + nodeContextMenu.Items.Add(new ToolStripMenuItem("Repair", Image("Command Prompt"), + new EventHandler(async (sender, e) => + { + if (!Program.IsProgramRunningDialog(this, selection)) return; + bool shouldReinstall = false; + byte[] properApi = null; + byte[] properApi64 = null; + foreach (string directory in selection.SteamApiDllDirectories) + { + directory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + shouldReinstall = File.Exists(cApi); + await InstallForm.UninstallCreamAPI(directory); + if (properApi is null && File.Exists(api) && !FileResourceExtensions.Equals(Properties.Resources.API, api)) + properApi = File.ReadAllBytes(api); + if (properApi64 is null && File.Exists(api64) && !FileResourceExtensions.Equals(Properties.Resources.API64, api64)) + properApi64 = File.ReadAllBytes(api64); + } + if (properApi is not null || properApi64 is not null) + { + bool neededRepair = false; + foreach (string directory in selection.SteamApiDllDirectories) + { + directory.GetApiComponents(out string api, out string api_o, out string api64, out string api64_o, out string cApi); + if (properApi is not null && FileResourceExtensions.Equals(Properties.Resources.API, api)) + { + properApi.Write(api); + neededRepair = true; + } + if (properApi64 is not null && FileResourceExtensions.Equals(Properties.Resources.API64, api64)) + { + properApi64.Write(api64); + neededRepair = true; + } + if (shouldReinstall) + await InstallForm.InstallCreamAPI(directory, selection); + } + if (neededRepair) + new DialogForm(this).Show("Paradox Launcher Repair", Icon, "Paradox Launcher successfully repaired!", "OK"); + else + new DialogForm(this).Show("Paradox Launcher Repair", SystemIcons.Information, "Paradox Launcher does not need to be repaired.", "OK"); + } + else + new DialogForm(this).Show("Paradox Launcher Repair", SystemIcons.Error, "Paradox Launcher repair failed!" + + "\n\nAn original Steamworks API file could not be found." + + "\nYou must reinstall Paradox Launcher to fix this issue.", "OK"); + }))); + } nodeContextMenu.Items.Add(new ToolStripSeparator()); nodeContextMenu.Items.Add(new ToolStripMenuItem("Open Root Directory", Image("File Explorer"), new EventHandler((sender, e) => Program.OpenDirectoryInFileExplorer(selection.RootDirectory)))); diff --git a/CreamInstaller/Program.cs b/CreamInstaller/Program.cs index 58f2aef..1048221 100644 --- a/CreamInstaller/Program.cs +++ b/CreamInstaller/Program.cs @@ -51,6 +51,15 @@ internal static class Program } } + internal static void GetApiComponents(this string directory, out string api, out string api_o, out string api64, out string api64_o, out string cApi) + { + api = directory + @"\steam_api.dll"; + api_o = directory + @"\steam_api_o.dll"; + api64 = directory + @"\steam_api64.dll"; + api64_o = directory + @"\steam_api64_o.dll"; + cApi = directory + @"\cream_api.ini"; + } + internal static bool IsGameBlocked(string name, string directory) { if (!BlockProtectedGames) return false; @@ -86,6 +95,12 @@ internal static class Program mutex.Close(); } + internal static Icon ToIcon(this Image image) + { + Bitmap dialogIconBitmap = new(image, new Size(image.Width, image.Height)); + return Icon.FromHandle(dialogIconBitmap.GetHicon()); + } + private static readonly string SteamAppImagesPath = "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/"; internal static async Task GetSteamIcon(int steamAppId, string iconStaticId) => await GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{iconStaticId}.jpg"); internal static async Task GetSteamClientIcon(int steamAppId, string clientIconStaticId) => await GetImageFromUrl(SteamAppImagesPath + $"/{steamAppId}/{clientIconStaticId}.ico"); diff --git a/CreamInstaller/Resources/FileResourceExtensions.cs b/CreamInstaller/Resources/FileResourceExtensions.cs index c908f25..7d03ece 100644 --- a/CreamInstaller/Resources/FileResourceExtensions.cs +++ b/CreamInstaller/Resources/FileResourceExtensions.cs @@ -9,4 +9,15 @@ internal static class FileResourceExtensions using FileStream file = new(filePath, FileMode.Create, FileAccess.Write); file.Write(resource); } + + internal static bool Equals(byte[] resource, string filePath) + { + byte[] file = File.ReadAllBytes(filePath); + if (resource.Length != file.Length) + return false; + for (int i = 0; i < resource.Length; i++) + if (resource[i] != file[i]) + return false; + return true; + } }