From 3395754db0baf482dac7b318d2208935abe17163 Mon Sep 17 00:00:00 2001 From: Kade Date: Sat, 11 Apr 2026 19:22:59 -0400 Subject: [PATCH 1/9] Installer: improve dllsBuild handling cleaned up, added new error for when dllsBuild is present but invalid --- ModAPI.Common/Update/UpdateManager.cs | 21 -------- Spore ModAPI Easy Installer/EasyInstaller.cs | 51 ++++++++++--------- .../Strings.Designer.cs | 34 ++++++++++--- Spore ModAPI Easy Installer/Strings.resx | 16 ++++-- 4 files changed, 67 insertions(+), 55 deletions(-) diff --git a/ModAPI.Common/Update/UpdateManager.cs b/ModAPI.Common/Update/UpdateManager.cs index 84c2d70..a823122 100644 --- a/ModAPI.Common/Update/UpdateManager.cs +++ b/ModAPI.Common/Update/UpdateManager.cs @@ -7,14 +7,12 @@ using System.Linq; using System.Reflection; using System.Windows; -using System.Xml; using ModAPI.Common.Dialog; namespace ModAPI.Common.Update { public static class UpdateManager { - public static bool Development = false; public static List LauncherKitUpdateUrls = new List { // Cloudflare R2 + Cache @@ -56,25 +54,6 @@ public static Version CurrentDllsBuild } } - public static bool HasValidDllsVersion(XmlDocument document) - { - var modNode = document.SelectSingleNode("/mod"); - - if (modNode != null) - { - Version requiredDllsVersion = null; - if (modNode.Attributes["dllsBuild"] != null) - Version.TryParse(modNode.Attributes["dllsBuild"].Value, out requiredDllsVersion); - - if (requiredDllsVersion != null && - requiredDllsVersion > CurrentDllsBuild) - { - return false; - } - } - return true; - } - public static void CheckForUpdates() { if ((Process.GetProcessesByName("SporeApp").Length > 0) || (Process.GetProcessesByName("SporeApp_ModAPIFix").Length > 0)) diff --git a/Spore ModAPI Easy Installer/EasyInstaller.cs b/Spore ModAPI Easy Installer/EasyInstaller.cs index 340bcb3..fd3fa4e 100755 --- a/Spore ModAPI Easy Installer/EasyInstaller.cs +++ b/Spore ModAPI Easy Installer/EasyInstaller.cs @@ -9,11 +9,8 @@ using System.Diagnostics; using System.IO; using System.IO.Compression; -using System.Linq; -using System.Net; using System.Threading; using System.Windows.Forms; -using System.Windows.Interop; using System.Xml; namespace Spore_ModAPI_Easy_Installer @@ -71,7 +68,7 @@ static void Main() ModList.Load(); // ensure we find Spore & GA as early as possible - if (PathDialogs.ProcessSpore() == null || + if (PathDialogs.ProcessSpore() == null || PathDialogs.ProcessGalacticAdventures() == null) { return; @@ -104,7 +101,7 @@ static void Main() FileType fileType = GetFileType(Path.GetFileName(inputPath)); string modName = Path.GetFileNameWithoutExtension(inputPath); ResultType result = ResultType.UnsupportedFile; - + try { @@ -382,7 +379,7 @@ private static ResultType ExtractSporemodZip(string inputFile, ModConfiguration } } - eventHandler?.Invoke(null, (int)((entriesExtracted / (float) numEntries) * 100.0f)); + eventHandler?.Invoke(null, (int)((entriesExtracted / (float)numEntries) * 100.0f)); entriesExtracted++; } } @@ -390,24 +387,20 @@ private static ResultType ExtractSporemodZip(string inputFile, ModConfiguration return ResultType.Success; } - private static bool CheckModCoreDllsVersion(ZipArchiveEntry xmlEntry) + private static Version GetModCoreDllsVersion(ZipArchiveEntry xmlEntry) { - try + using (var stream = xmlEntry.Open()) { - using (var stream = xmlEntry.Open()) - { - var document = new XmlDocument(); - document.Load(stream); + var document = new XmlDocument(); + document.Load(stream); - if (!UpdateManager.HasValidDllsVersion(document)) - { - return false; - } + var modNode = document.SelectSingleNode("/mod"); + if (modNode != null && modNode.Attributes["dllsBuild"] != null) + { + return Version.Parse(modNode.Attributes["dllsBuild"].Value); } } - catch { - } - return true; + return null; } static ResultType TryExecuteInstaller(string inputFile, string modName) @@ -420,12 +413,24 @@ static ResultType TryExecuteInstaller(string inputFile, string modName) if (xmlEntry != null) { - if (!CheckModCoreDllsVersion(xmlEntry)) + Version modCoreDllsVersion = null; + try { - MessageBox.Show($"\"{modName}\"{Strings.UnsupportedDllVersion}", - Strings.UnsupportedDllVersionTitle); + modCoreDllsVersion = GetModCoreDllsVersion(xmlEntry); + } + // If the version cannot be read due to an exception, show an error and don't install the mod + catch + { + MessageBox.Show($"\"{modName}\"{Strings.InvalidDllVersion}", Strings.InvalidDllVersionTitle); return ResultType.ModNotInstalled; } + // If the version can be read but is outdated, show an error and don't install the mod + if (modCoreDllsVersion != null && modCoreDllsVersion > UpdateManager.CurrentDllsBuild) + { + MessageBox.Show($"\"{modName}\"{Strings.OutdatedDllVersion.Replace("$REQUIREDVERSION$", modCoreDllsVersion.ToString()).Replace("$CURRENTVERSION$", UpdateManager.CurrentDllsBuild.ToString())}", Strings.OutdatedDllVersionTitle); + return ResultType.ModNotInstalled; + } + // If the version is not specified, continue installing (the value is optional because not all mods use the ModAPI SDK) string modPath = Path.Combine(Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(), "ModConfigs", modName); if (Directory.Exists(modPath)) @@ -563,7 +568,7 @@ static ResultType InstallSporemod(string inputFile, string modName) // the Installer existed but there was a problem return result; } - + } static void RemoveModFiles(ModConfiguration mod) diff --git a/Spore ModAPI Easy Installer/Strings.Designer.cs b/Spore ModAPI Easy Installer/Strings.Designer.cs index 72b1b18..464e16c 100644 --- a/Spore ModAPI Easy Installer/Strings.Designer.cs +++ b/Spore ModAPI Easy Installer/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace Spore_ModAPI_Easy_Installer { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -132,6 +132,24 @@ internal static string InstallingModTitle { } } + /// + /// Looks up a localized string similar to cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help.. + /// + internal static string InvalidDllVersion { + get { + return ResourceManager.GetString("InvalidDllVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error: mod cannot be installed. + /// + internal static string InvalidDllVersionTitle { + get { + return ResourceManager.GetString("InvalidDllVersionTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The mod ". /// @@ -187,21 +205,23 @@ internal static string ModNotInstalled2 { } /// - /// Looks up a localized string similar to cannot be installed, because it requires a greater version of the ModAPI Core DLLs. - ///Please, restart the launcher and allow it to update.. + /// Looks up a localized string similar to cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. + /// + ///Required ModAPI DLLs version: $REQUIREDVERSION$ + ///Current ModAPI DLLs version: $CURRENTVERSION$. /// - internal static string UnsupportedDllVersion { + internal static string OutdatedDllVersion { get { - return ResourceManager.GetString("UnsupportedDllVersion", resourceCulture); + return ResourceManager.GetString("OutdatedDllVersion", resourceCulture); } } /// /// Looks up a localized string similar to Error: mod cannot be installed. /// - internal static string UnsupportedDllVersionTitle { + internal static string OutdatedDllVersionTitle { get { - return ResourceManager.GetString("UnsupportedDllVersionTitle", resourceCulture); + return ResourceManager.GetString("OutdatedDllVersionTitle", resourceCulture); } } } diff --git a/Spore ModAPI Easy Installer/Strings.resx b/Spore ModAPI Easy Installer/Strings.resx index 1dc9937..d8cd2b2 100644 --- a/Spore ModAPI Easy Installer/Strings.resx +++ b/Spore ModAPI Easy Installer/Strings.resx @@ -156,14 +156,22 @@ " could not be installed. - - cannot be installed, because it requires a greater version of the ModAPI Core DLLs. -Please, restart the launcher and allow it to update. + + Error: mod cannot be installed - + Error: mod cannot be installed Installation completed + + cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. + +Required ModAPI DLLs version: $REQUIREDVERSION$ +Current ModAPI DLLs version: $CURRENTVERSION$ + + + cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help. + \ No newline at end of file From 071553f58e77703dc77a7c75631136acb0ea72e7 Mon Sep 17 00:00:00 2001 From: Kade Date: Thu, 23 Apr 2026 20:12:50 -0400 Subject: [PATCH 2/9] Common: Overhaul game path handling Streamlined and centralized logic for finding game folder paths, updated logic to more closely match the game, removed path dialog as it should be obsolete, updated related strings --- ModAPI.Common/CommonStrings.Designer.cs | 65 ++--- ModAPI.Common/CommonStrings.resx | 35 ++- ModAPI.Common/LauncherSettings.cs | 100 ------- ModAPI.Common/ModApi.Common.csproj | 2 - ModAPI.Common/PathDialogs.cs | 156 ----------- ModAPI.Common/SporePath.cs | 258 ++++++++---------- Spore ModAPI Easy Installer/EasyInstaller.cs | 36 +-- .../XmlInstallerWindow.xaml.cs | 39 +-- .../EasyUninstaller.cs | 82 ++---- Spore ModAPI Launcher/Program.cs | 8 +- 10 files changed, 196 insertions(+), 585 deletions(-) delete mode 100644 ModAPI.Common/LauncherSettings.cs delete mode 100644 ModAPI.Common/PathDialogs.cs diff --git a/ModAPI.Common/CommonStrings.Designer.cs b/ModAPI.Common/CommonStrings.Designer.cs index 1f4b070..1a7c2c8 100644 --- a/ModAPI.Common/CommonStrings.Designer.cs +++ b/ModAPI.Common/CommonStrings.Designer.cs @@ -19,7 +19,7 @@ namespace ModAPI.Common { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class CommonStrings { @@ -39,7 +39,7 @@ internal CommonStrings() { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModApi.Common.CommonStrings", typeof(CommonStrings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ModAPI.Common.CommonStrings", typeof(CommonStrings).Assembly); resourceMan = temp; } return resourceMan; @@ -61,7 +61,7 @@ internal CommonStrings() { } /// - /// Looks up a localized string similar to An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to run modern mods. Do you want to download it?. + /// Looks up a localized string similar to An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Do you want to download it?. /// public static string DllsUpdateAvailable { get { @@ -88,20 +88,24 @@ public static string Error { } /// - /// Looks up a localized string similar to Galactic Adventures was not found.. + /// Looks up a localized string similar to Spore or Spore Galactic Adventures was not found. + /// + ///Both Spore and Spore Galactic Adventures must be installed from disc, EA App, Steam, or GOG. + /// + ///Please see https://launcherkit.sporecommunity.com/support for further help.. /// - public static string GalacticAdventuresNotFound { + public static string GameNotFound { get { - return ResourceManager.GetString("GalacticAdventuresNotFound", resourceCulture); + return ResourceManager.GetString("GameNotFound", resourceCulture); } } /// - /// Looks up a localized string similar to Galactic Adventures was not found. Please specify the installation folder manually (usually called SPORE_EP1 or SPORE Galactic Adventures).. + /// Looks up a localized string similar to Game not found. /// - public static string GalacticAdventuresNotFoundSpecifyManual { + public static string GameNotFoundTitle { get { - return ResourceManager.GetString("GalacticAdventuresNotFoundSpecifyManual", resourceCulture); + return ResourceManager.GetString("GameNotFoundTitle", resourceCulture); } } @@ -115,47 +119,24 @@ public static string InvalidPath { } /// - /// Looks up a localized string similar to Invalid path. + /// Looks up a localized string similar to Please launch Spore Galactic Adventures once from Steam. This will complete the installation of the game, and allow the Launcher Kit to work. + /// + ///Afterwards, always use the ModAPI Launcher to play the game. + /// + ///If you continue to get this message, or the game is not installed from Steam, then the game is likely not installed properly. Please see https://launcherkit.sporecommunity.com/support for further help.. /// - public static string InvalidPathTitle { + public static string SteamDownloadedButNotLaunched { get { - return ResourceManager.GetString("InvalidPathTitle", resourceCulture); + return ResourceManager.GetString("SteamDownloadedButNotLaunched", resourceCulture); } } /// - /// Looks up a localized string similar to Spore was not found.. + /// Looks up a localized string similar to Steam installation not completed. /// - public static string SporeNotFound { + public static string SteamDownloadedButNotLaunchedTitle { get { - return ResourceManager.GetString("SporeNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Spore was not found. Please specify the installation folder manually (usually called SPORE).. - /// - public static string SporeNotFoundSpecifyManual { - get { - return ResourceManager.GetString("SporeNotFoundSpecifyManual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Steam was not found.. - /// - public static string SteamNotFound { - get { - return ResourceManager.GetString("SteamNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Steam was not found. Please specify the installation folder manually (usually C:\Program Files (x86)\Steam).. - /// - public static string SteamNotFoundSpecifyManual { - get { - return ResourceManager.GetString("SteamNotFoundSpecifyManual", resourceCulture); + return ResourceManager.GetString("SteamDownloadedButNotLaunchedTitle", resourceCulture); } } diff --git a/ModAPI.Common/CommonStrings.resx b/ModAPI.Common/CommonStrings.resx index c884b75..a97c3f2 100644 --- a/ModAPI.Common/CommonStrings.resx +++ b/ModAPI.Common/CommonStrings.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to run modern mods. Do you want to download it? + An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Do you want to download it? ModAPI DLLs Update Available @@ -126,29 +126,28 @@ Error - - Galactic Adventures was not found. + + Spore or Spore Galactic Adventures was not found. + +Both Spore and Spore Galactic Adventures must be installed from disc, EA App, Steam, or GOG. + +Please see https://launcherkit.sporecommunity.com/support for further help. - - Galactic Adventures was not found. Please specify the installation folder manually (usually called SPORE_EP1 or SPORE Galactic Adventures). + + Game not found The path specified does not exist or is invalid. - - Invalid path - - - Spore was not found. - - - Spore was not found. Please specify the installation folder manually (usually called SPORE). - - - Steam was not found. + + Please launch Spore Galactic Adventures once from Steam. This will complete the installation of the game, and allow the Launcher Kit to work. + +Afterwards, always use the ModAPI Launcher to play the game. + +If you continue to get this message, or the game is not installed from Steam, then the game is likely not installed properly. Please see https://launcherkit.sporecommunity.com/support for further help. - - Steam was not found. Please specify the installation folder manually (usually C:\Program Files (x86)\Steam). + + Steam installation not completed Unauthorized access. Try executing the launcher with Administrator Privileges (Right-click > "Run as Administrator"). diff --git a/ModAPI.Common/LauncherSettings.cs b/ModAPI.Common/LauncherSettings.cs deleted file mode 100644 index 4618403..0000000 --- a/ModAPI.Common/LauncherSettings.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; - -using System.IO; - -using System.Xml; - -namespace ModAPI.Common -{ - public static class LauncherSettings - { - private static string FileName = "LauncherSettings.config"; - - private static Dictionary _dictionary = new Dictionary(); - - public static string GamePath - { - get - { - string value = null; - _dictionary.TryGetValue("GamePath", out value); - return value; - } - set - { - _dictionary["GamePath"] = value; - } - } - - public static string SporeGamePath - { - get - { - string value = null; - _dictionary.TryGetValue("SporeGamePath", out value); - return value; - } - set - { - _dictionary["SporeGamePath"] = value; - } - } - - private static string GetConfigPath() - { - var programPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); - return Path.Combine(programPath, FileName); - } - - public static bool Load() - { - string path = GetConfigPath(); - if (File.Exists(path)) - { - string xmlIn = File.ReadAllText(path); - if (!String.IsNullOrEmpty(xmlIn)) - { - var document = new XmlDocument(); - document.LoadXml(xmlIn); - - foreach (XmlNode node in document.ChildNodes) - { - if (node.Name == "Settings") - { - foreach (XmlNode settingNode in node.ChildNodes) - { - _dictionary[settingNode.Name] = settingNode.InnerText; - } - } - } - } - - return true; - } - else - { - return false; - } - } - - public static void Save() - { - string path = GetConfigPath(); - var document = new XmlDocument(); - - var mainNode = document.CreateElement("Settings"); - document.AppendChild(mainNode); - - foreach (KeyValuePair entry in _dictionary) - { - var node = document.CreateElement(entry.Key); - node.InnerText = entry.Value; - - mainNode.AppendChild(node); - } - - document.Save(path); - } - } -} diff --git a/ModAPI.Common/ModApi.Common.csproj b/ModAPI.Common/ModApi.Common.csproj index f8947ac..c292173 100644 --- a/ModAPI.Common/ModApi.Common.csproj +++ b/ModAPI.Common/ModApi.Common.csproj @@ -62,8 +62,6 @@ - - diff --git a/ModAPI.Common/PathDialogs.cs b/ModAPI.Common/PathDialogs.cs deleted file mode 100644 index 1826a2d..0000000 --- a/ModAPI.Common/PathDialogs.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.Threading; -using System.IO; -using System.Windows.Forms; -using System; - -namespace ModAPI.Common -{ - public static class PathDialogs - { - // Returns path to SporebinEP1 or null - private static string _galacticAdventuresPath = null; - public static string ProcessGalacticAdventures() - { - if (_galacticAdventuresPath != null) - { - return _galacticAdventuresPath; - } - - try - { - // attempt to retrieve spore path from registry - string path = SporePath.GetFromRegistry(SporePath.Game.GalacticAdventures); - if (!String.IsNullOrEmpty(path) && Directory.Exists(path)) - { - path = SporePath.MoveToSporebinEP1(path); - } - - // fallback to specified game path - if (String.IsNullOrEmpty(path) || !Directory.Exists(path)) - { - path = LauncherSettings.GamePath; - if (!String.IsNullOrEmpty(path) && Directory.Exists(path)) - { - path = SporePath.MoveToSporebinEP1(path); - } - } - - // ask the user when fallback wasn't found - if (String.IsNullOrEmpty(path) || !Directory.Exists(path)) - { - var result = MessageBox.Show(CommonStrings.GalacticAdventuresNotFoundSpecifyManual, CommonStrings.GalacticAdventuresNotFound, - MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); - - if (result == DialogResult.OK) - { - path = ShowGalacticAdventuresChooserDialog(); - } - } - - _galacticAdventuresPath = path; - } - catch (Exception) - { - return null; - } - - return _galacticAdventuresPath; - } - - // Returns path to Sporebin or null - private static string _coreSporePath = null; - public static string ProcessSpore() - { - if (_coreSporePath != null) - { - return _coreSporePath; - } - - try - { - // attempt to retrieve spore path from registry - string path = SporePath.GetFromRegistry(SporePath.Game.Spore); - if (!String.IsNullOrEmpty(path) && Directory.Exists(path)) - { - path = SporePath.MoveToSporebin(path); - } - - // fallback to specified game path - if (String.IsNullOrEmpty(path) || !Directory.Exists(path)) - { - path = LauncherSettings.SporeGamePath; - if (!String.IsNullOrEmpty(path) && Directory.Exists(path)) - { - path = SporePath.MoveToSporebin(path); - } - } - - // ask the user when fallback wasn't found - if (String.IsNullOrEmpty(path) || !Directory.Exists(path)) - { - var result = MessageBox.Show(CommonStrings.SporeNotFoundSpecifyManual, CommonStrings.SporeNotFound, - MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); - - if (result == DialogResult.OK) - { - path = ShowSporeChooserDialog(); - } - } - - _coreSporePath = path; - } - catch (Exception) - { - return null; - } - - return _coreSporePath; - } - - // -- DIALOGS -- // - private static string ShowGalacticAdventuresChooserDialog() - { - string path = null; - Thread thread = new Thread(() => - { - var dialog = new FolderBrowserDialog(); - if (dialog.ShowDialog() == DialogResult.OK) - { - path = dialog.SelectedPath; - // move the path to SporebinEP1 - path = SporePath.MoveToSporebinEP1(path); - LauncherSettings.GamePath = path; - LauncherSettings.Save(); - } - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - thread.Join(); - - return path; - } - - private static string ShowSporeChooserDialog() - { - string path = null; - Thread thread = new Thread(() => - { - var dialog = new FolderBrowserDialog(); - if (dialog.ShowDialog() == DialogResult.OK) - { - path = dialog.SelectedPath; - // move the path to SporebinEP1 - path = SporePath.MoveToSporebin(path); - LauncherSettings.SporeGamePath = path; - LauncherSettings.Save(); - } - }); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - thread.Join(); - - return path; - } - } - -} diff --git a/ModAPI.Common/SporePath.cs b/ModAPI.Common/SporePath.cs index 7e632d5..8857485 100644 --- a/ModAPI.Common/SporePath.cs +++ b/ModAPI.Common/SporePath.cs @@ -1,10 +1,12 @@ using Microsoft.Win32; using System.IO; +using System.Windows; namespace ModAPI.Common { public static class SporePath { + public enum Game { None, @@ -12,39 +14,76 @@ public enum Game Spore, CreepyAndCute } - public static readonly string[] RegistryValues = { "InstallLoc", "Install Dir" }; - public static readonly string[] RegistryKeys = { - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Electronic Arts\\SPORE_EP1", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\SPORE_EP1" - }; + // Some things for Steam + public static readonly string SteamAppsKey = @"HKEY_CURRENT_USER\Software\Valve\Steam\Apps\"; + public static readonly string GalacticAdventuresSteamID = "24720"; - public static readonly string[] SporeRegistryKeys = { - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Electronic Arts\\SPORE", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\SPORE" - }; + /// + /// Gets the path to the SporebinEP1 folder that contains SporeApp.exe for Galactic Adventures. + /// The path will NOT have a trailing slash. + /// If the path cannot be found or does not exist (typically if GA is not properly installed), this will return null. + /// + public static string GetSporebinEP1Path() + { + // Use GA's data path as a starting point, as SporebinEP1 is always next to whatever GA's data dir is + var dataPath = GetDataPath(Game.GalacticAdventures); + if (dataPath == null) + { + return null; + } - public static readonly string[] CCRegistryKeys = { - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Electronic Arts\\SPORE(TM) Creepy & Cute Parts Pack", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Electronic Arts\\SPORE(TM) Creepy & Cute Parts Pack" - }; + var gameRootPath = Directory.GetParent(dataPath).ToString(); + var binPath = Path.Combine(gameRootPath, "SporebinEP1"); + // Verify that this folder actually contains SporeApp.exe + if (!File.Exists(Path.Combine(binPath, "SporeApp.exe"))) + { + return null; + } - public static readonly string RegistryDataDir = "DataDir"; // Steam/GoG users don't have InstallLoc nor Install Dir + return binPath; + } + /// + /// Gets the path to the Data folder that contains packages for the specified game. + /// The path will NOT have a trailing slash. + /// If the path cannot be found or does not exist (typically if the game is not properly installed), this will return null. + /// + public static string GetDataPath(Game game) + { + // If registry value is missing or not a string, return null + if (!(GetFromRegistry(game, "DataDir") is string path)) + { + return null; + } - // Some things for Steam - public static readonly string[] SteamRegistryKeys = { - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Valve\\Steam", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Valve\\Steam" - }; + // Remove "" if necessary + path = FixPath(path); - public static readonly string SteamRegistryValue = "InstallPath"; + // Fix slashes if necessary (GOG .22 has mixed slashes) + path = Path.GetFullPath(path); - public static readonly string SteamAppsKey = @"HKEY_CURRENT_USER\Software\Valve\Steam\Apps\"; + // Remove trailing slash if present + path = path.TrimEnd(Path.DirectorySeparatorChar); - public static readonly string GalacticAdventuresSteamID = "24720"; + // Verify that folder actually exists + if (!Directory.Exists(path)) + { + return null; + } + return path; + } + + /// + /// Returns true if the specified game is properly installed, by checking that its Data folder can be found and exists. + /// This matches the behavior of the game itself, which shows a "Data directory missing or corrupt" error and exits if the Data folder (located via hardcoded registry key) cannot be found or does not exist. + /// + public static bool IsInstalled(Game game) + { + return GetDataPath(game) != null; + } // remove "" if necessary private static string FixPath(string path) @@ -66,156 +105,83 @@ public static bool SporeIsInstalledOnSteam() return result != null && ((int)result != 0); } - public static string GetFromRegistry(Game game) + /// + /// Returns true if Spore and GA are properly installed. + /// If either game is not properly installed, returns false, and optionally shows an error message to the user. If the game is installed from Steam but hasn't been launched to complete the installation, shows a specific error message about launching the game on Steam. + /// + /// + public static bool IsGameInstalled(bool showMessageWhenFalse) { - if (game == Game.GalacticAdventures) - { - return GetFromRegistry(RegistryKeys); - } - else if (game == Game.Spore) - { - return GetFromRegistry(SporeRegistryKeys); - } - else + if (IsInstalled(Game.Spore) && IsInstalled(Game.GalacticAdventures)) { - return GetFromRegistry(CCRegistryKeys); + return true; } - } - - public static string GetFromRegistry(string[] keys) - { - string result = null; - - foreach (string key in keys) + if (showMessageWhenFalse) { - foreach (string value in RegistryValues) + // Steam doesn't run the install script to create the registry keys until the game is launched from Steam for the first time + if (SporeIsInstalledOnSteam()) { - result = (string)Registry.GetValue(key, value, null); - if (result != null) - { - - return FixPath(result); - } + MessageBox.Show(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle, MessageBoxButton.OK, MessageBoxImage.Information); } - } - - // not found? try with DataDir; some users only have that one - foreach (string key in RegistryKeys) - { - result = (string)Registry.GetValue(key, RegistryDataDir, null); - if (result != null) + else { - - return FixPath(result); + MessageBox.Show(CommonStrings.GameNotFound, CommonStrings.GameNotFoundTitle, MessageBoxButton.OK, MessageBoxImage.Error); } } - - return null; + return false; } - - public static string GetFromRegistry(string[] keys, string[] values) + /// + /// Gets the value of a registry key for the specified game, checking both 32-bit and 64-bit paths. + /// If the key does not exist, returns null. + /// + private static object GetFromRegistry(Game game, string valueName) { - string result = null; - - foreach (string key in keys) + foreach (string key in GetRegistryKeySuffixes(game)) { - foreach (string value in values) + var value = GetFromRegistry(key, valueName); + if (value != null) { - result = (string)Registry.GetValue(key, value, null); - if (result != null) - { - - return FixPath(result); - } + return value; } } - return null; } - // If a path ends in \\, Directory.GetParent willl return the same folder!! - public static string GetRealParent(string path) + /// + /// Gets the value of a registry key from HKLM\Software, checking both 32-bit and 64-bit paths. + /// If the key does not exist, returns null. + /// + private static object GetFromRegistry(string keyName, string valueName) { - if (path.EndsWith("\\")) - { - path = Directory.GetParent(path).ToString(); - } + // Registry key prefix varies depending on 64-bit or 32-bit system + const string keyPrefix64 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\"; + const string keyPrefix32 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\"; - return Directory.GetParent(path).ToString(); + var value = Registry.GetValue(keyPrefix64 + keyName, valueName, null); + return value ?? Registry.GetValue(keyPrefix32 + keyName, valueName, null); } - // This method returns the path to the folder that contains the executable - public static string MoveToSporebinEP1(string path, bool recursive = true) + /// + /// Gets the possible registry subkeys within HKLM\Software for the specified game. + /// These keys contain the DataDir value, which is used by the game to locate its own packages. + /// + private static string[] GetRegistryKeySuffixes(Game game) { - if (File.Exists(Path.Combine(path, "SporeApp.exe"))) - { - return path; - } - - string sporeBinEP1Path = Path.Combine(path, "SporebinEP1"); - if (Directory.Exists(sporeBinEP1Path)) - { - return sporeBinEP1Path; - } - - if (recursive) - { - // check if the user selected another folder (for example, "Data") - return MoveToSporebinEP1(GetRealParent(path), false); + switch (game) + { + case Game.GalacticAdventures: + return new[] { "Electronic Arts\\SPORE_EP1" }; + case Game.Spore: + return new[] { "Electronic Arts\\SPORE" }; + case Game.CreepyAndCute: + // Disc/Origin/EA use the former, Steam/GOG use the latter, game hardcodes both + return new[] { "Electronic Arts\\SPORE(TM) Creepy & Cute Parts Pack", "Electronic Arts\\SPORE Creepy and Cute Parts Pack" }; + default: + return new string[] { }; } - - return null; } - // This method returns the path to the folder that contains the executable - public static string MoveToSporebin(string path, bool recursive = true) - { - if (File.Exists(Path.Combine(path, "SporeApp.exe"))) - { - return path; - } - - string sporeBinPath = Path.Combine(path, "Sporebin"); - if (Directory.Exists(sporeBinPath)) - { - return sporeBinPath; - } - - if (recursive) - { - // check if the user selected another folder (for example, "Data") - return MoveToSporebin(GetRealParent(path), false); - } - - return null; - } - - public static string MoveToData(Game game, string installationDirectory) - { - if (game == Game.Spore) - { - return Path.Combine(installationDirectory, "Data"); - } - else if (game == Game.GalacticAdventures) - { - // Steam and GoG uses DataEP1 - string outputPath = Path.Combine(installationDirectory, "DataEP1"); - if (Directory.Exists(outputPath)) - { - return outputPath; - } - else - { - return Path.Combine(installationDirectory, "Data"); - } - } - else - { - // Creepy and Cute uses the installation path itself - return installationDirectory; - } - } } -} +} \ No newline at end of file diff --git a/Spore ModAPI Easy Installer/EasyInstaller.cs b/Spore ModAPI Easy Installer/EasyInstaller.cs index fd3fa4e..446faa7 100755 --- a/Spore ModAPI Easy Installer/EasyInstaller.cs +++ b/Spore ModAPI Easy Installer/EasyInstaller.cs @@ -64,12 +64,10 @@ static void Main() else { Application.EnableVisualStyles(); - LauncherSettings.Load(); ModList.Load(); // ensure we find Spore & GA as early as possible - if (PathDialogs.ProcessSpore() == null || - PathDialogs.ProcessGalacticAdventures() == null) + if (!SporePath.IsGameInstalled(true)) { return; } @@ -214,10 +212,10 @@ static string GetOutputPath(FileType type) return Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(); case FileType.Package: - return GetGADataPath(); + return SporePath.GetDataPath(SporePath.Game.GalacticAdventures); case FileType.Spore_Package: - return GetSporeDataPath(); + return SporePath.GetDataPath(SporePath.Game.Spore); default: return null; @@ -244,32 +242,6 @@ static string GetOutputPath(string pathType) } } - static string GetGADataPath() - { - string path = PathDialogs.ProcessGalacticAdventures(); - - if (path != null) - { - // now we have the path to SporebinEP1; move it to Data - path = SporePath.MoveToData(SporePath.Game.GalacticAdventures, SporePath.GetRealParent(path)); - } - - return path; - } - - static string GetSporeDataPath() - { - string path = PathDialogs.ProcessSpore(); - - if (path != null) - { - // now we have the path to Sporebin; move it to Data - path = SporePath.MoveToData(SporePath.Game.Spore, SporePath.GetRealParent(path)); - } - - return path; - } - static ResultType InstallPackage(string inputFile, string modName) { ResultType result = ResultType.Success; @@ -597,7 +569,7 @@ static string GetErrorMessage(ResultType errorType) switch (errorType) { case ResultType.UnsupportedFile: return Strings.ErrorUnsupportedFile; - case ResultType.GalacticAdventuresNotFound: return CommonStrings.GalacticAdventuresNotFound; + case ResultType.GalacticAdventuresNotFound: return CommonStrings.GameNotFound; case ResultType.UnauthorizedAccess: return CommonStrings.UnauthorizedAccess; case ResultType.InvalidPath: return CommonStrings.InvalidPath; diff --git a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs index 53d61da..3b15576 100644 --- a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs +++ b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs @@ -58,9 +58,9 @@ public partial class XmlInstallerWindow : DecoratableWindow int _installerMode = 0; //0 = show components list, 1 = don't show components list bool _isModMatched = false; bool _dontUseLegacyPackagePlacement = false; - public static string GaDataPath = SporePath.MoveToData(SporePath.Game.GalacticAdventures, SporePath.GetRealParent(PathDialogs.ProcessGalacticAdventures())); + public static string GaDataPath = SporePath.GetDataPath(SporePath.Game.GalacticAdventures); - public static string SporeDataPath = SporePath.MoveToData(SporePath.Game.Spore, SporePath.GetRealParent(PathDialogs.ProcessSpore())); + public static string SporeDataPath = SporePath.GetDataPath(SporePath.Game.Spore); private ResultType _result = ResultType.ModNotInstalled; @@ -70,10 +70,11 @@ public XmlInstallerWindow(string modName, bool configure, bool uninstall) //LauncherSettings.Load(); installerWindows.Add(this); - if (!Directory.Exists(SporeDataPath)) + // TODO: do we need to show an error if data folders not found? PathDialogs would've shown a dialog, but could return null, so this code would've continued with a null path + /*if (!Directory.Exists(SporeDataPath)) PathDialogs.ProcessSpore(); if (!Directory.Exists(GaDataPath)) - PathDialogs.ProcessGalacticAdventures(); + PathDialogs.ProcessGalacticAdventures();*/ ModName = modName.Trim('"'); XmlInstallerCancellation.Cancellation.Add(ModName, false); @@ -151,8 +152,8 @@ private void PrepareInstaller() { ModInfoVersion = Version.Parse(Document.SelectSingleNode("/mod").Attributes["installerSystemVersion"].Value); - if ((ModInfoVersion == new Version(1, 0, 0, 0)) || - (ModInfoVersion == new Version(1, 0, 1, 0)) || + if ((ModInfoVersion == new Version(1, 0, 0, 0)) || + (ModInfoVersion == new Version(1, 0, 1, 0)) || (ModInfoVersion == new Version(1, 0, 1, 1)) || (ModInfoVersion == new Version(1, 0, 1, 2))) { @@ -180,7 +181,7 @@ private void PrepareInstaller() if (Document.SelectSingleNode("/mod").Attributes["mode"].Value == "compatOnly") _installerMode = 1; } - else if ((ModInfoVersion == new Version(1, 0, 1, 0)) || + else if ((ModInfoVersion == new Version(1, 0, 1, 0)) || (ModInfoVersion == new Version(1, 0, 1, 1)) || (ModInfoVersion == new Version(1, 0, 1, 2))) { @@ -209,7 +210,7 @@ private void PrepareInstaller() throw new Exception("This Mod's installer does not have a Unique Identifier. Please inform the mod's developer of this."); ModConfiguration[] configs = EasyInstaller.ModList.ModConfigurations.ToArray(); - + foreach (ModConfiguration mod in configs) { if (mod.Unique == ModUnique) @@ -619,7 +620,7 @@ private async void StartInstallationButton_Click(object sender, RoutedEventArgs InstallationCompleteDescriptionTextBlock.Text = "The mod \"" + ModDisplayName + "\" has been installed."; _installerState = 1; CollapseButtons(); - + var anim = new DoubleAnimation() { Duration = _time, @@ -1311,7 +1312,7 @@ private void XmlInstallerWindow_Closing(object sender, System.ComponentModel.Can { if (Directory.Exists(ModConfigPath)) DeleteFolder(ModConfigPath); - + } XmlInstallerCancellation.Cancellation[ModName] = true; } @@ -1331,7 +1332,7 @@ private void XmlInstallerWindow_Closed(object sender, EventArgs e) if (_isConfigurator) Environment.Exit((int)_result); } - + private void SplashBorder_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DragMove(); @@ -1386,14 +1387,16 @@ public void Remove() new DirectoryInfo(Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString()), new DirectoryInfo(Path.Combine(Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(), "mLibs")) }; - foreach (DirectoryInfo info in infos) { - List files = info.EnumerateFiles(RemovalFileNames[i]).ToList(); - - foreach (FileInfo f in files) + foreach (DirectoryInfo info in infos) { - if (File.Exists(f.FullName)) - File.Delete(f.FullName); - } } + List files = info.EnumerateFiles(RemovalFileNames[i]).ToList(); + + foreach (FileInfo f in files) + { + if (File.Exists(f.FullName)) + File.Delete(f.FullName); + } + } } } } diff --git a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs index f7f0f0a..5806dc4 100644 --- a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs +++ b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using System.Windows.Forms; using System.Diagnostics; @@ -19,10 +17,6 @@ namespace Spore_ModAPI_Easy_Uninstaller public static class EasyUninstaller { - private static string SporeDataPath; - private static string SporeDataEP1Path; - private static string DllPath; - private static InstalledMods Mods; private static UninstallerForm Form; @@ -44,11 +38,8 @@ static void Main() Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - LauncherSettings.Load(); - // ensure we find Spore & GA as early as possible - if (PathDialogs.ProcessSpore() == null || - PathDialogs.ProcessGalacticAdventures() == null) + if (!SporePath.IsGameInstalled(true)) { return; } @@ -122,61 +113,18 @@ public static void UninstallMods(Dictionary mods) } } - private static void CheckSporeDataEP1Path(string modName) - { - if (SporeDataEP1Path == null) - { - string path = PathDialogs.ProcessGalacticAdventures(); - if (path == null && modName != null) - { - throw new Exception(Strings.CouldNotUninstall + " \"" + modName + "\"\n" + CommonStrings.GalacticAdventuresNotFound); - } - else - { - SporeDataEP1Path = SporePath.MoveToData(SporePath.Game.GalacticAdventures, SporePath.GetRealParent(path)); - } - } - } - - private static void CheckSporeDataPath(string modName) - { - if (SporeDataPath == null) - { - string path = PathDialogs.ProcessSpore(); - if (path == null && modName != null) - { - throw new Exception(Strings.CouldNotUninstall + " \"" + modName + "\"\n" + CommonStrings.SporeNotFound); - } - else - { - SporeDataPath = SporePath.MoveToData(SporePath.Game.Spore, SporePath.GetRealParent(path)); - } - } - } - - private static void CheckDllPath() - { - if (DllPath == null) - { - DllPath = Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(); - } - } - - private static string GetOutputPath(string pathType, string modName) + private static string GetOutputPath(string pathType) { switch (pathType) { case "GalacticAdventures": - CheckSporeDataEP1Path(modName); - return SporeDataEP1Path; + return SporePath.GetDataPath(SporePath.Game.GalacticAdventures); case "Spore": - CheckSporeDataPath(modName); - return SporeDataPath; + return SporePath.GetDataPath(SporePath.Game.Spore); case "None": - CheckDllPath(); - return DllPath; + return Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(); default: return null; @@ -188,19 +136,21 @@ private static void RemoveModFiles(ModConfiguration mod) { foreach (InstalledFile file in mod.InstalledFiles) { - string outputPath = GetOutputPath(file.PathType, mod.Name); + string outputPath = GetOutputPath(file.PathType); - if (outputPath != null) + if (outputPath == null) { - string outputFile = Path.Combine(outputPath, file.Name); - string outputFile2 = Path.Combine(outputPath, "mLibs", file.Name); - if (File.Exists(outputFile)) - File.Delete(outputFile); - if (File.Exists(outputFile2)) //if ((file.Name.Contains("-disk") || file.Name.Contains("-steam") || file.Name.Contains("-steam_patched"))) - File.Delete(outputFile2); + throw new Exception(Strings.CouldNotUninstall + " \"" + mod.Name + "\"\n" + CommonStrings.GameNotFound); } + + string outputFile = Path.Combine(outputPath, file.Name); + string outputFile2 = Path.Combine(outputPath, "mLibs", file.Name); + if (File.Exists(outputFile)) + File.Delete(outputFile); + if (File.Exists(outputFile2)) //if ((file.Name.Contains("-disk") || file.Name.Contains("-steam") || file.Name.Contains("-steam_patched"))) + File.Delete(outputFile2); } - + string modConfigPath = Path.Combine(Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString(), "ModConfigs", mod.Name); if (Directory.Exists(modConfigPath)) Directory.Delete(modConfigPath, true); diff --git a/Spore ModAPI Launcher/Program.cs b/Spore ModAPI Launcher/Program.cs index 3b276ba..dccd545 100644 --- a/Spore ModAPI Launcher/Program.cs +++ b/Spore ModAPI Launcher/Program.cs @@ -50,11 +50,9 @@ static void Main() { UpdateManager.CheckForUpdates(); Application.EnableVisualStyles(); - LauncherSettings.Load(); // ensure we find Spore & GA as early as possible - if (PathDialogs.ProcessSpore() == null || - PathDialogs.ProcessGalacticAdventures() == null) + if (!SporePath.IsGameInstalled(true)) { return; } @@ -78,7 +76,7 @@ void Execute() // Before, we used Steam to launch the game and tried to find the new process and inject it. // However, when the injection happens the game already executed a bit, so mods fail. // Instead, we create a steam_appid.txt that allows us to execute SporeApp.exe directly - SporebinPath = PathDialogs.ProcessGalacticAdventures(); + SporebinPath = SporePath.GetSporebinEP1Path(); // use the default path for now (we might have to use a different one for Origin) this.ExecutablePath = Path.Combine(this.SporebinPath, "SporeApp.exe"); @@ -126,7 +124,7 @@ void Execute() } else { - throw new Exception(CommonStrings.GalacticAdventuresNotFound); + throw new Exception(CommonStrings.GameNotFound); } } From 3663660dfedaa38aa4c1e807681d30d2357c5493 Mon Sep 17 00:00:00 2001 From: Kade Date: Fri, 24 Apr 2026 04:48:54 -0400 Subject: [PATCH 3/9] All: Overhaul and centralize error UX Moved most errors to a common system that shows support info, add check for LAA and show error if used on incompatible game version --- ModAPI.Common/CommonStrings.Designer.cs | 56 ++++-- ModAPI.Common/CommonStrings.resx | 32 +++- ModAPI.Common/GameVersion.cs | 40 ++++- ModAPI.Common/LAAUtils.cs | 38 +++++ ModAPI.Common/ModApi.Common.csproj | 2 + ModAPI.Common/SporePath.cs | 4 +- ModAPI.Common/SupportInfo.cs | 160 ++++++++++++++++++ ModAPI.Common/Update/UpdateManager.cs | 28 +-- Spore ModAPI Easy Installer/EasyInstaller.cs | 14 +- .../Strings.Designer.cs | 79 ++++----- Spore ModAPI Easy Installer/Strings.resx | 35 ++-- .../EasyUninstaller.cs | 4 +- .../Strings.Designer.cs | 17 +- Spore ModAPI Easy Uninstaller/Strings.resx | 9 +- .../UninstallerForm.Designer.cs | 53 +++--- .../UninstallerForm.cs | 4 +- Spore ModAPI Launcher/Program.cs | 42 ++--- Spore ModAPI Launcher/Strings.resx | 70 ++++++-- Spore ModAPI Launcher/Strings1.Designer.cs | 106 +++++++++--- 19 files changed, 589 insertions(+), 204 deletions(-) create mode 100644 ModAPI.Common/LAAUtils.cs create mode 100644 ModAPI.Common/SupportInfo.cs diff --git a/ModAPI.Common/CommonStrings.Designer.cs b/ModAPI.Common/CommonStrings.Designer.cs index 1a7c2c8..8a4de4e 100644 --- a/ModAPI.Common/CommonStrings.Designer.cs +++ b/ModAPI.Common/CommonStrings.Designer.cs @@ -61,7 +61,11 @@ internal CommonStrings() { } /// - /// Looks up a localized string similar to An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Do you want to download it?. + /// Looks up a localized string similar to An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Would you like to install it now? + /// + ///Current Launcher Kit version: $CURRENTLK$ + ///Current ModAPI DLLs version: $CURRENTDLLS$ + ///New ModAPI DLLs version: $NEWDLLS$. /// public static string DllsUpdateAvailable { get { @@ -79,11 +83,22 @@ public static string DllsUpdateAvailableTitle { } /// - /// Looks up a localized string similar to Error. + /// Looks up a localized string similar to Please close Spore before using the Launcher Kit again. + /// + ///If you have just closed Spore, wait a moment for the game to fully exit. If you continue to get this error, please restart your computer.. /// - public static string Error { + public static string GameAlreadyRunning { get { - return ResourceManager.GetString("Error", resourceCulture); + return ResourceManager.GetString("GameAlreadyRunning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Game already running. + /// + public static string GameAlreadyRunningTitle { + get { + return ResourceManager.GetString("GameAlreadyRunningTitle", resourceCulture); } } @@ -118,6 +133,27 @@ public static string InvalidPath { } } + /// + /// Looks up a localized string similar to An update to the Spore ModAPI Launcher Kit is now available. Would you like to install it now? + /// + ///Current Launcher Kit version: $CURRENTLK$ + ///New Launcher Kit version: $NEWLKS$. + /// + public static string LKUpdateAvailable { + get { + return ResourceManager.GetString("LKUpdateAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launcher Kit Update Available. + /// + public static string LKUpdateAvailableTitle { + get { + return ResourceManager.GetString("LKUpdateAvailableTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please launch Spore Galactic Adventures once from Steam. This will complete the installation of the game, and allow the Launcher Kit to work. /// @@ -141,20 +177,20 @@ public static string SteamDownloadedButNotLaunchedTitle { } /// - /// Looks up a localized string similar to Unauthorized access. Try executing the launcher with Administrator Privileges (Right-click > "Run as Administrator").. + /// Looks up a localized string similar to The Launcher Kit could not connect to the update service. Try again in a few minutes, or check https://launcherkit.sporecommunity.com/support for help.. /// - public static string UnauthorizedAccess { + public static string UpdateCheckFailed { get { - return ResourceManager.GetString("UnauthorizedAccess", resourceCulture); + return ResourceManager.GetString("UpdateCheckFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Error: Unauthorized access. + /// Looks up a localized string similar to Launcher Kit Update. /// - public static string UnauthorizedAccessTitle { + public static string UpdateCheckFailedTitle { get { - return ResourceManager.GetString("UnauthorizedAccessTitle", resourceCulture); + return ResourceManager.GetString("UpdateCheckFailedTitle", resourceCulture); } } diff --git a/ModAPI.Common/CommonStrings.resx b/ModAPI.Common/CommonStrings.resx index a97c3f2..1a99a9b 100644 --- a/ModAPI.Common/CommonStrings.resx +++ b/ModAPI.Common/CommonStrings.resx @@ -118,13 +118,22 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Do you want to download it? + An update to the ModAPI DLLs is available. It includes new features and bugfixes, and is required to use newer mods. Would you like to install it now? + +Current Launcher Kit version: $CURRENTLK$ +Current ModAPI DLLs version: $CURRENTDLLS$ +New ModAPI DLLs version: $NEWDLLS$ ModAPI DLLs Update Available - - Error + + Please close Spore before using the Launcher Kit again. + +If you have just closed Spore, wait a moment for the game to fully exit. If you continue to get this error, please restart your computer. + + + Game already running Spore or Spore Galactic Adventures was not found. @@ -139,6 +148,15 @@ Please see https://launcherkit.sporecommunity.com/support for further help. The path specified does not exist or is invalid. + + An update to the Spore ModAPI Launcher Kit is now available. Would you like to install it now? + +Current Launcher Kit version: $CURRENTLK$ +New Launcher Kit version: $NEWLKS$ + + + Launcher Kit Update Available + Please launch Spore Galactic Adventures once from Steam. This will complete the installation of the game, and allow the Launcher Kit to work. @@ -149,11 +167,11 @@ If you continue to get this message, or the game is not installed from Steam, th Steam installation not completed - - Unauthorized access. Try executing the launcher with Administrator Privileges (Right-click > "Run as Administrator"). + + The Launcher Kit could not connect to the update service. Try again in a few minutes, or check https://launcherkit.sporecommunity.com/support for help. - - Error: Unauthorized access + + Launcher Kit Update ModAPI DLLs are updating to version diff --git a/ModAPI.Common/GameVersion.cs b/ModAPI.Common/GameVersion.cs index 512d034..98eb88c 100644 --- a/ModAPI.Common/GameVersion.cs +++ b/ModAPI.Common/GameVersion.cs @@ -22,7 +22,7 @@ public enum GameVersionType public static class GameVersion { - private static readonly int[] ExecutableSizes = { + private static readonly int[] ExecutableSizes = { 24909584, // Disc 24898224, // Origin_March2017 24906040, // EA_October2024 @@ -113,5 +113,43 @@ public static string GetNewDLLName(GameVersionType type) return null; } } + + public static string GetFriendlyVersionName(GameVersionType type) + { + switch (type) + { + case GameVersionType.Disc: + return "Disc + Patch 5.1, July 2009"; + case GameVersionType.Origin_March2017: + return "Origin, March 2017"; + case GameVersionType.EA_October2024: + return "EA App, October 2024"; + case GameVersionType.Steam_March2017: + return "GOG/Steam, March 2017"; + case GameVersionType.GOG_October2024: + return "GOG, October 2024"; + case GameVersionType.Steam_October2024: + return "Steam, October 2024"; + default: + return "Unknown"; + } + } + + /// + /// Returns true if this version type is compatible with LAA (Large Address Aware, aka 4GB patch). + /// + public static bool IsLAACompatible(GameVersionType type) + { + switch (type) + { + // Steam_October2024 has steamstub DRM which prevents modification of the exe, thus preventing the game from running if LAA is set in PE header + case GameVersionType.Steam_October2024: + return false; + // No known issues with any other versions + default: + return true; + } + } + } } diff --git a/ModAPI.Common/LAAUtils.cs b/ModAPI.Common/LAAUtils.cs new file mode 100644 index 0000000..2980e1b --- /dev/null +++ b/ModAPI.Common/LAAUtils.cs @@ -0,0 +1,38 @@ +using System.IO; + +namespace ModAPI.Common +{ + public static class LAAUtils + { + private const long POINTER_TO_PE_HEADER = 0x3C; + private const long CHARACTERISTICS_OFFSET_FROM_PE_HEADER = 0x16; + private const ushort IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x20; + + /// + /// Returns true if the specified path is a LAA (Large Address Aware, aka 4GB patched) executable. + /// The specified path must be a valid Windows executable, otherwise behavior is undefined. + /// + public static bool IsLAA(string path) + { + // Based on https://stackoverflow.com/a/9056757 + + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + using (var reader = new BinaryReader(stream)) + { + // Locate PE header + reader.BaseStream.Position = POINTER_TO_PE_HEADER; + var peHeaderPosition = reader.ReadInt32(); + + // Locate Characteristics field + reader.BaseStream.Position = peHeaderPosition + CHARACTERISTICS_OFFSET_FROM_PE_HEADER; + + // Check if the LAA flag is set + var characteristics = reader.ReadUInt16(); + return (characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) != 0; + } + } + } + + } +} \ No newline at end of file diff --git a/ModAPI.Common/ModApi.Common.csproj b/ModAPI.Common/ModApi.Common.csproj index c292173..151f716 100644 --- a/ModAPI.Common/ModApi.Common.csproj +++ b/ModAPI.Common/ModApi.Common.csproj @@ -62,10 +62,12 @@ + + diff --git a/ModAPI.Common/SporePath.cs b/ModAPI.Common/SporePath.cs index 8857485..c7527b9 100644 --- a/ModAPI.Common/SporePath.cs +++ b/ModAPI.Common/SporePath.cs @@ -122,11 +122,11 @@ public static bool IsGameInstalled(bool showMessageWhenFalse) // Steam doesn't run the install script to create the registry keys until the game is launched from Steam for the first time if (SporeIsInstalledOnSteam()) { - MessageBox.Show(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle, MessageBoxButton.OK, MessageBoxImage.Information); + SupportInfo.ShowInfo(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle, true, false); } else { - MessageBox.Show(CommonStrings.GameNotFound, CommonStrings.GameNotFoundTitle, MessageBoxButton.OK, MessageBoxImage.Error); + SupportInfo.ShowError(CommonStrings.GameNotFound, CommonStrings.GameNotFoundTitle, false, false); } } return false; diff --git a/ModAPI.Common/SupportInfo.cs b/ModAPI.Common/SupportInfo.cs new file mode 100644 index 0000000..7975fc5 --- /dev/null +++ b/ModAPI.Common/SupportInfo.cs @@ -0,0 +1,160 @@ +using ModAPI.Common.Update; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Windows.Forms; + +namespace ModAPI.Common +{ + public static class SupportInfo + { + /// + /// The current version of the Launcher Kit. + /// + public static Version LauncherKitVersion => UpdateManager.CurrentVersion; + + /// + /// The current version of the Launcher Kit, as a string containing a minimum of three version components (i.e. "1.2.3"). A fourth component (revision) will be included only if it is non-zero. + /// + public static string LauncherKitVersionString => LauncherKitVersion.Revision == 0 ? LauncherKitVersion.ToString(3) : LauncherKitVersion.ToString(); + + /// + /// The current version of the ModAPI DLLs that will be injected into the game. + /// + public static Version ModAPIDllsVersion => UpdateManager.CurrentDllsBuild; + + /// + /// The current version of the ModAPI DLLs, as a string containing a minimum of three version components (i.e. "1.2.3"). A fourth component (revision) will be included only if it is non-zero. + /// + public static string ModAPIDllsVersionString => ModAPIDllsVersion.Revision == 0 ? ModAPIDllsVersion.ToString(3) : ModAPIDllsVersion.ToString(); + + /// + /// The folder path where the Launcher Kit is installed. This folder contains the executables for the Easy Installer, Easy Uninstaller, and Launcher. + /// By default, this is "%programdata%\Spore ModAPI Launcher Kit". + /// The path will NOT have a trailing slash. + /// + public static String LauncherKitPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + + + /// + /// The file version of SporeApp.exe. + /// If SporeApp.exe could not be found, this will return null. + /// + public static string GameVersionString + { + get + { + var sporebinEP1Path = SporePath.GetSporebinEP1Path(); + if (sporebinEP1Path == null) + { + return null; + } + // If not null, SporebinEP1Path is guaranteed to exist and contain SporeApp.exe + var sporeAppPath = Path.Combine(sporebinEP1Path, "SporeApp.exe"); + return FileVersionInfo.GetVersionInfo(sporeAppPath).FileVersion; + } + } + + /// + /// The version type of the game. This will be one of the following values: + /// "Disc + Patch 5.1, July 2009", + /// "Origin, March 2017", + /// "EA App, October 2024", + /// "GOG/Steam, March 2017", + /// "GOG, October 2024", + /// "Steam, October 2024", + /// "Unknown". + /// If SporeApp.exe could not be found, this will return null. + /// + public static string GameVersionTypeString + { + get + { + var sporebinEP1Path = SporePath.GetSporebinEP1Path(); + if (sporebinEP1Path == null) + { + return null; + } + var sporeAppPath = Path.Combine(sporebinEP1Path, "SporeApp.exe"); + var versionType = GameVersion.DetectVersion(sporeAppPath); + return GameVersion.GetFriendlyVersionName(versionType); + } + } + + /// + /// Gets a string with the game version and version type, i.e. "3.1.0.29 - GOG, October 2024, LAA". + /// If SporeApp.exe could not be found, this will return "Game not found". + /// + public static string GameFullVersionInfoString + { + get + { + if (GameVersionString == null || GameVersionTypeString == null) + { + return "Game not found"; + } + + var sporeAppPath = Path.Combine(SporePath.GetSporebinEP1Path(), "SporeApp.exe"); + var laaString = LAAUtils.IsLAA(sporeAppPath) ? ", LAA" : ""; + + return $"{GameVersionString} - {GameVersionTypeString}{laaString}"; + } + } + + + + private static DialogResult ShowMessageBox(string message, string title, bool showGameInfo, bool showPaths, MessageBoxButtons buttons, MessageBoxIcon icon) + { + message += "\n"; + + // Append game info + if (showGameInfo) + { + message += $"\nSpore version: {GameFullVersionInfoString}"; + if (showPaths) + { + message += $"\nSpore path: {SporePath.GetSporebinEP1Path()}"; + } + } + + // Append LK info + message += $"\nLauncher Kit version: {LauncherKitVersionString}\nModAPI DLLs Version: {ModAPIDllsVersionString}"; + if (showPaths) + { + message += $"\nLauncher Kit path: {LauncherKitPath}"; + } + + return MessageBox.Show(message, title, buttons, icon); + } + + /// + /// Shows an info message box to the user. Information about the Launcher Kit will be included, and optionally information about the game. + /// Info message boxes should be used for information that the user should be aware of, but is not causing a problem. + /// + public static DialogResult ShowInfo(string message, string title, bool showGameInfo = true, bool showPaths = true, MessageBoxButtons buttons = MessageBoxButtons.OK) + { + return ShowMessageBox(message, title, showGameInfo, showPaths, buttons, MessageBoxIcon.Information); + } + + /// + /// Shows a warning message box to the user. Information about the Launcher Kit will be included, and optionally information about the game. + /// Warning message boxes should be used for potential problems, where the program can continue, but further problems are likely to occur. + /// + public static DialogResult ShowWarning(string message, string title, bool showGameInfo = true, bool showPaths = true, MessageBoxButtons buttons = MessageBoxButtons.OK) + { + return ShowMessageBox(message, title, showGameInfo, showPaths, buttons, MessageBoxIcon.Warning); + } + + /// + /// Shows an error message box to the user. Information about the Launcher Kit will be included, and optionally information about the game. + /// Error message boxes should be used for problems that prevent the program from continuing. + /// + public static DialogResult ShowError(string message, string title, bool showGameInfo = true, bool showPaths = true, MessageBoxButtons buttons = MessageBoxButtons.OK) + { + return ShowMessageBox(message, title, showGameInfo, showPaths, buttons, MessageBoxIcon.Error); + } + + } +} \ No newline at end of file diff --git a/ModAPI.Common/Update/UpdateManager.cs b/ModAPI.Common/Update/UpdateManager.cs index a823122..28cac88 100644 --- a/ModAPI.Common/Update/UpdateManager.cs +++ b/ModAPI.Common/Update/UpdateManager.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Windows; +using System.Windows.Forms; using ModAPI.Common.Dialog; namespace ModAPI.Common.Update @@ -58,7 +58,7 @@ public static void CheckForUpdates() { if ((Process.GetProcessesByName("SporeApp").Length > 0) || (Process.GetProcessesByName("SporeApp_ModAPIFix").Length > 0)) { - MessageBox.Show("Please close Spore before attempting to use the Spore ModAPI Launcher Kit again. If you have just closed Spore, wait a moment for the game to fully exit.", string.Empty); + SupportInfo.ShowWarning(CommonStrings.GameAlreadyRunning, CommonStrings.GameAlreadyRunningTitle, true, false); Process.GetCurrentProcess().Kill(); } @@ -100,7 +100,7 @@ public static void CheckForUpdates() { List exceptions = new List(); bool didDownload = false; - + // Try to download the update info file from the override path first if (File.Exists(UpdaterOverridePath)) { @@ -161,7 +161,7 @@ public static void CheckForUpdates() if (!didDownload) { ShowUpdateCheckFailedMessage(exceptions); - + // early return when failed return; } @@ -174,9 +174,10 @@ public static void CheckForUpdates() { if (Version.Parse(updateInfoLines[1]) > CurrentVersion) { - string versionString = "Current version: " + CurrentVersion + "\nNew version: " + updateInfoLines[1]; + string currentLKVersion = SupportInfo.LauncherKitVersionString; + string newLKVersion = updateInfoLines[1]; - if (MessageBox.Show("An update to the Spore ModAPI Launcher Kit is now available. Would you like to install it now?\n\n" + versionString, "Update Available", MessageBoxButton.YesNo) == MessageBoxResult.Yes) + if (MessageBox.Show(CommonStrings.LKUpdateAvailable.Replace("$NEWLK", newLKVersion).Replace("$CURRENTLK", currentLKVersion), CommonStrings.LKUpdateAvailableTitle, MessageBoxButtons.YesNo) == DialogResult.Yes) { if (bool.Parse(updateInfoLines[2])) { @@ -185,7 +186,7 @@ public static void CheckForUpdates() else { var dialog = new ProgressDialog( - "Spore ModAPI Launcher Kit is updating to " + updateInfoLines[1], + "Spore ModAPI Launcher Kit is updating to " + newLKVersion, "Spore ModAPI Launcher Kit updating", (s, e) => { @@ -210,7 +211,7 @@ public static void CheckForUpdates() foreach (string arg in args) currentArgs += "\"" + arg.TrimEnd('\\') + "\" "; - string argOnePath = Directory.GetParent(System.Reflection.Assembly.GetEntryAssembly().Location).ToString().TrimEnd('\\'); + string argOnePath = Directory.GetParent(Assembly.GetEntryAssembly().Location).ToString().TrimEnd('\\'); if (!argOnePath.EndsWith(" ")) argOnePath = argOnePath + " "; @@ -234,8 +235,11 @@ public static void CheckForUpdates() if (DllsUpdater.HasDllsUpdate(out var githubRelease)) { - var result = MessageBox.Show(CommonStrings.DllsUpdateAvailable, CommonStrings.DllsUpdateAvailableTitle, MessageBoxButton.YesNo); - if (result == MessageBoxResult.Yes) + string newDllsVersion = githubRelease.tag_name; + string currentDllsVersion = SupportInfo.ModAPIDllsVersionString; + string currentLKVersion = SupportInfo.LauncherKitVersionString; + var result = MessageBox.Show(CommonStrings.DllsUpdateAvailable.Replace("$NEWDLLS", newDllsVersion).Replace("$CURRENTDLLS", currentDllsVersion).Replace("$CURRENTLK$", currentLKVersion), CommonStrings.DllsUpdateAvailableTitle, MessageBoxButtons.YesNo); + if (result == DialogResult.Yes) { var dialog = new ProgressDialog( CommonStrings.UpdatingDllsDialog + githubRelease.tag_name, @@ -279,12 +283,12 @@ static void ShowUpdateCheckFailedMessage(List exceptions) } - MessageBox.Show("The Launcher Kit could not connect to the update service. Try again in a few minutes, or check https://launcherkit.sporecommunity.com/support for help.\n\nCurrent version: "+ CurrentVersion + "\n\n" + exceptionText); + SupportInfo.ShowWarning(CommonStrings.UpdateCheckFailed + "\n\n" + exceptionText, CommonStrings.UpdateCheckFailedTitle, false, true); } static void ShowUnrecognizedUpdateInfoVersionMessage() { - MessageBox.Show("This update to the Spore ModAPI Launcher Kit must be downloaded manually."); + SupportInfo.ShowInfo("This update to the Spore ModAPI Launcher Kit must be downloaded manually. Please visit https://launcherkit.sporecommunity.com/support for more information.", CommonStrings.UpdateCheckFailedTitle, false, true); } } } \ No newline at end of file diff --git a/Spore ModAPI Easy Installer/EasyInstaller.cs b/Spore ModAPI Easy Installer/EasyInstaller.cs index 446faa7..e23e639 100755 --- a/Spore ModAPI Easy Installer/EasyInstaller.cs +++ b/Spore ModAPI Easy Installer/EasyInstaller.cs @@ -393,13 +393,13 @@ static ResultType TryExecuteInstaller(string inputFile, string modName) // If the version cannot be read due to an exception, show an error and don't install the mod catch { - MessageBox.Show($"\"{modName}\"{Strings.InvalidDllVersion}", Strings.InvalidDllVersionTitle); + SupportInfo.ShowWarning(Strings.InvalidDllVersion.Replace("$MODNAME$", modName), Strings.InvalidDllVersionTitle, false, false); return ResultType.ModNotInstalled; } // If the version can be read but is outdated, show an error and don't install the mod if (modCoreDllsVersion != null && modCoreDllsVersion > UpdateManager.CurrentDllsBuild) { - MessageBox.Show($"\"{modName}\"{Strings.OutdatedDllVersion.Replace("$REQUIREDVERSION$", modCoreDllsVersion.ToString()).Replace("$CURRENTVERSION$", UpdateManager.CurrentDllsBuild.ToString())}", Strings.OutdatedDllVersionTitle); + SupportInfo.ShowWarning(Strings.OutdatedDllVersion.Replace("$MODNAME$", modName).Replace("$REQUIREDVERSION$", modCoreDllsVersion.ToString()), Strings.OutdatedDllVersionTitle, false, false); return ResultType.ModNotInstalled; } // If the version is not specified, continue installing (the value is optional because not all mods use the ModAPI SDK) @@ -485,7 +485,7 @@ static ResultType InstallSporemod(string inputFile, string modName) Thread thread = new Thread(() => { - var dialog = new ProgressDialog(Strings.ModIsInstalling1 + modName + "\" is being installed", Strings.InstallingModTitle, (s, e) => + var dialog = new ProgressDialog(Strings.InstallingMod.Replace("$MODNAME$", modName), Strings.InstallingModTitle, (s, e) => { try { @@ -570,7 +570,7 @@ static string GetErrorMessage(ResultType errorType) { case ResultType.UnsupportedFile: return Strings.ErrorUnsupportedFile; case ResultType.GalacticAdventuresNotFound: return CommonStrings.GameNotFound; - case ResultType.UnauthorizedAccess: return CommonStrings.UnauthorizedAccess; + case ResultType.UnauthorizedAccess: return Strings.UnauthorizedAccess; case ResultType.InvalidPath: return CommonStrings.InvalidPath; default: @@ -585,11 +585,11 @@ static string GetResultText(ResultType result, string modName, string errorStrin if (result == ResultType.Success) { // show message to the user - return Strings.ModInstalled1 + modName + Strings.ModInstalled2; + return Strings.ModInstalled.Replace("$MODNAME$", modName); } else if (result == ResultType.ModNotInstalled) { - return Strings.ModInstalled1 + modName + Strings.CancelledInstallation; + return Strings.ModCancelled.Replace("$MODNAME$", modName); } else { @@ -599,7 +599,7 @@ static string GetResultText(ResultType result, string modName, string errorStrin } // show message to the user //MessageBox.Show(Strings.ModNotInstalled1 + modName + Strings.ModNotInstalled2 + " " + errorString, Strings.InstallationCancelled); - return Strings.ModNotInstalled1 + modName + Strings.ModNotInstalled2 + " " + errorString; + return Strings.ModNotInstalled.Replace("$MODNAME$", modName) + "\n\n" + errorString; } } diff --git a/Spore ModAPI Easy Installer/Strings.Designer.cs b/Spore ModAPI Easy Installer/Strings.Designer.cs index 464e16c..3c63b6a 100644 --- a/Spore ModAPI Easy Installer/Strings.Designer.cs +++ b/Spore ModAPI Easy Installer/Strings.Designer.cs @@ -60,15 +60,6 @@ internal Strings() { } } - /// - /// Looks up a localized string similar to " cancelled installation. - /// - internal static string CancelledInstallation { - get { - return ResourceManager.GetString("CancelledInstallation", resourceCulture); - } - } - /// /// Looks up a localized string similar to Copying file. /// @@ -79,7 +70,7 @@ internal static string CopyingFile { } /// - /// Looks up a localized string similar to The file given is not supported. Only .sporemod, .package and .exe files are supported.. + /// Looks up a localized string similar to This is not a valid mod file. Only .sporemod and .package files are supported.. /// internal static string ErrorUnsupportedFile { get { @@ -123,6 +114,15 @@ internal static string InstallationCompleted { } } + /// + /// Looks up a localized string similar to The mod "$MODNAME$" is being installed, please wait.... + /// + internal static string InstallingMod { + get { + return ResourceManager.GetString("InstallingMod", resourceCulture); + } + } + /// /// Looks up a localized string similar to Installing mod.... /// @@ -151,64 +151,36 @@ internal static string InvalidDllVersionTitle { } /// - /// Looks up a localized string similar to The mod ". + /// Looks up a localized string similar to Installation of the mod "$MODNAME$" was cancelled.. /// - internal static string ModInstalled1 { + internal static string ModCancelled { get { - return ResourceManager.GetString("ModInstalled1", resourceCulture); + return ResourceManager.GetString("ModCancelled", resourceCulture); } } /// - /// Looks up a localized string similar to " has been installed successfully.. + /// Looks up a localized string similar to The mod "$MODNAME$" has been installed.. /// - internal static string ModInstalled2 { + internal static string ModInstalled { get { - return ResourceManager.GetString("ModInstalled2", resourceCulture); + return ResourceManager.GetString("ModInstalled", resourceCulture); } } /// - /// Looks up a localized string similar to The mod ". + /// Looks up a localized string similar to Something went wrong when installing the mod "$MODNAME$". This mod was not installed.. /// - internal static string ModIsInstalling1 { + internal static string ModNotInstalled { get { - return ResourceManager.GetString("ModIsInstalling1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to " is being installed. Please, wait until it finishes.. - /// - internal static string ModIsInstalling2 { - get { - return ResourceManager.GetString("ModIsInstalling2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The mod ". - /// - internal static string ModNotInstalled1 { - get { - return ResourceManager.GetString("ModNotInstalled1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to " could not be installed.. - /// - internal static string ModNotInstalled2 { - get { - return ResourceManager.GetString("ModNotInstalled2", resourceCulture); + return ResourceManager.GetString("ModNotInstalled", resourceCulture); } } /// /// Looks up a localized string similar to cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. /// - ///Required ModAPI DLLs version: $REQUIREDVERSION$ - ///Current ModAPI DLLs version: $CURRENTVERSION$. + ///Required ModAPI DLLs version: $REQUIREDVERSION$ or higher. /// internal static string OutdatedDllVersion { get { @@ -217,12 +189,21 @@ internal static string OutdatedDllVersion { } /// - /// Looks up a localized string similar to Error: mod cannot be installed. + /// Looks up a localized string similar to Outdated ModAPI DLLs version. /// internal static string OutdatedDllVersionTitle { get { return ResourceManager.GetString("OutdatedDllVersionTitle", resourceCulture); } } + + /// + /// Looks up a localized string similar to The Easy Installer does not currently have permission to install or change mods. You may need to right-click the Easy Installer and "Run as Administrator".. + /// + internal static string UnauthorizedAccess { + get { + return ResourceManager.GetString("UnauthorizedAccess", resourceCulture); + } + } } } diff --git a/Spore ModAPI Easy Installer/Strings.resx b/Spore ModAPI Easy Installer/Strings.resx index d8cd2b2..729025c 100644 --- a/Spore ModAPI Easy Installer/Strings.resx +++ b/Spore ModAPI Easy Installer/Strings.resx @@ -121,7 +121,7 @@ Copying file - The file given is not supported. Only .sporemod, .package and .exe files are supported. + This is not a valid mod file. Only .sporemod and .package files are supported. Extracting file @@ -132,32 +132,23 @@ Choose the mod to be installed - - " cancelled installation + + Installation of the mod "$MODNAME$" was cancelled. Installing mod... - - The mod " + + The mod "$MODNAME$" has been installed. - - " has been installed successfully. + + The mod "$MODNAME$" is being installed, please wait... - - The mod " - - - " is being installed. Please, wait until it finishes. - - - The mod " - - - " could not be installed. + + Something went wrong when installing the mod "$MODNAME$". This mod was not installed. - Error: mod cannot be installed + Outdated ModAPI DLLs version Error: mod cannot be installed @@ -168,10 +159,12 @@ cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. -Required ModAPI DLLs version: $REQUIREDVERSION$ -Current ModAPI DLLs version: $CURRENTVERSION$ +Required ModAPI DLLs version: $REQUIREDVERSION$ or higher cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help. + + The Easy Installer does not currently have permission to install or change mods. You may need to right-click the Easy Installer and "Run as Administrator". + \ No newline at end of file diff --git a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs index 5806dc4..57ebea9 100644 --- a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs +++ b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs @@ -88,11 +88,11 @@ public static void UninstallMods(Dictionary mods) } catch (UnauthorizedAccessException) { - MessageBox.Show(CommonStrings.UnauthorizedAccess, Strings.CouldNotUninstall, MessageBoxButtons.OK, MessageBoxIcon.Error); + SupportInfo.ShowWarning(Strings.UnauthorizedAccess, Strings.CouldNotUninstall, false); } catch (Exception ex) { - MessageBox.Show(ex.Message, Strings.CouldNotUninstall, MessageBoxButtons.OK, MessageBoxIcon.Error); + SupportInfo.ShowWarning(ex.Message, Strings.CouldNotUninstall, false); } if (successfulMods.Count > 0) diff --git a/Spore ModAPI Easy Uninstaller/Strings.Designer.cs b/Spore ModAPI Easy Uninstaller/Strings.Designer.cs index 7dfb0ac..89c23c4 100644 --- a/Spore ModAPI Easy Uninstaller/Strings.Designer.cs +++ b/Spore ModAPI Easy Uninstaller/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace Spore_ModAPI_Easy_Uninstaller { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -61,7 +61,7 @@ internal Strings() { } /// - /// Looks up a localized string similar to Are you sure you want to uninstall the selected mods and all their components?. + /// Looks up a localized string similar to Are you sure you want to uninstall the selected mods and all of their components?. /// internal static string AreYouSure { get { @@ -106,7 +106,7 @@ internal static string ConfirmationNeeded { } /// - /// Looks up a localized string similar to Error, could not uninstall mod. + /// Looks up a localized string similar to Could not uninstall mod. /// internal static string CouldNotUninstall { get { @@ -124,7 +124,7 @@ internal static string InstalledMods { } /// - /// Looks up a localized string similar to The following mods were uninstalled successfully:. + /// Looks up a localized string similar to The following mods were uninstalled:. /// internal static string ModsWereUninstalled { get { @@ -132,6 +132,15 @@ internal static string ModsWereUninstalled { } } + /// + /// Looks up a localized string similar to The Easy Uninstaller does not currently have permission to uninstall or change mods. You may need to right-click the Easy Uninstaller and "Run as Administrator".. + /// + internal static string UnauthorizedAccess { + get { + return ResourceManager.GetString("UnauthorizedAccess", resourceCulture); + } + } + /// /// Looks up a localized string similar to Uninstallation successful. /// diff --git a/Spore ModAPI Easy Uninstaller/Strings.resx b/Spore ModAPI Easy Uninstaller/Strings.resx index e286cb3..89895d9 100644 --- a/Spore ModAPI Easy Uninstaller/Strings.resx +++ b/Spore ModAPI Easy Uninstaller/Strings.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Are you sure you want to uninstall the selected mods and all their components? + Are you sure you want to uninstall the selected mods and all of their components? Cancel @@ -133,13 +133,16 @@ Confirmation needed - Error, could not uninstall mod + Could not uninstall mod Installed Mods - The following mods were uninstalled successfully: + The following mods were uninstalled: + + + The Easy Uninstaller does not currently have permission to uninstall or change mods. You may need to right-click the Easy Uninstaller and "Run as Administrator". Uninstallation successful diff --git a/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs b/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs index f21f126..6ebf486 100644 --- a/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs +++ b/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs @@ -35,11 +35,11 @@ private void InitializeComponent() this.btnCancel = new System.Windows.Forms.Button(); this.btnUninstall = new System.Windows.Forms.Button(); this.dataGridView1 = new System.Windows.Forms.DataGridView(); - this.label2 = new System.Windows.Forms.Label(); this.MainColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ModNames = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ModDisplayNames = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ModConfiguration = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.label2 = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); this.SuspendLayout(); // @@ -47,18 +47,20 @@ private void InitializeComponent() // this.label1.AutoSize = true; this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.Location = new System.Drawing.Point(13, 13); + this.label1.Location = new System.Drawing.Point(17, 16); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(278, 18); + this.label1.Size = new System.Drawing.Size(349, 24); this.label1.TabIndex = 0; this.label1.Text = "Choose the mod(s) you want to uninstall:"; // // btnCancel // this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnCancel.Location = new System.Drawing.Point(405, 377); + this.btnCancel.Location = new System.Drawing.Point(540, 464); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.Size = new System.Drawing.Size(100, 28); this.btnCancel.TabIndex = 1; this.btnCancel.Text = "Cancel"; this.btnCancel.UseVisualStyleBackColor = true; @@ -67,9 +69,10 @@ private void InitializeComponent() // btnUninstall // this.btnUninstall.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.btnUninstall.Location = new System.Drawing.Point(239, 377); + this.btnUninstall.Location = new System.Drawing.Point(319, 464); + this.btnUninstall.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.btnUninstall.Name = "btnUninstall"; - this.btnUninstall.Size = new System.Drawing.Size(160, 23); + this.btnUninstall.Size = new System.Drawing.Size(213, 28); this.btnUninstall.TabIndex = 2; this.btnUninstall.Text = "Uninstall"; this.btnUninstall.UseVisualStyleBackColor = true; @@ -107,27 +110,20 @@ private void InitializeComponent() dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2; - this.dataGridView1.Location = new System.Drawing.Point(13, 50); + this.dataGridView1.Location = new System.Drawing.Point(17, 62); + this.dataGridView1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.dataGridView1.Name = "dataGridView1"; this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowHeadersWidth = 51; this.dataGridView1.RowTemplate.Height = 30; this.dataGridView1.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; - this.dataGridView1.Size = new System.Drawing.Size(464, 311); + this.dataGridView1.Size = new System.Drawing.Size(619, 383); this.dataGridView1.TabIndex = 5; // - // label2 - // - this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(10, 382); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(45, 13); - this.label2.TabIndex = 6; - this.label2.Text = "Version "; - // // MainColumn // this.MainColumn.HeaderText = "Uninstall"; + this.MainColumn.MinimumWidth = 6; this.MainColumn.Name = "MainColumn"; this.MainColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; this.MainColumn.Width = 30; @@ -147,26 +143,41 @@ private void InitializeComponent() this.ModDisplayNames.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; this.ModDisplayNames.DataPropertyName = "DisplayName"; this.ModDisplayNames.HeaderText = "Mod Display Names"; + this.ModDisplayNames.MinimumWidth = 6; this.ModDisplayNames.Name = "ModDisplayNames"; this.ModDisplayNames.ReadOnly = true; // // ModConfiguration // this.ModConfiguration.HeaderText = "Mod Configuration"; + this.ModConfiguration.MinimumWidth = 6; this.ModConfiguration.Name = "ModConfiguration"; this.ModConfiguration.Resizable = System.Windows.Forms.DataGridViewTriState.True; + this.ModConfiguration.Width = 125; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(18, 449); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(56, 16); + this.label2.TabIndex = 6; + this.label2.Text = "Version "; // // UninstallerForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(492, 412); + this.ClientSize = new System.Drawing.Size(656, 507); this.Controls.Add(this.label2); this.Controls.Add(this.dataGridView1); this.Controls.Add(this.btnUninstall); this.Controls.Add(this.btnCancel); this.Controls.Add(this.label1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); this.Name = "UninstallerForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Spore ModAPI Easy Uninstaller"; diff --git a/Spore ModAPI Easy Uninstaller/UninstallerForm.cs b/Spore ModAPI Easy Uninstaller/UninstallerForm.cs index e3c7242..98cd733 100644 --- a/Spore ModAPI Easy Uninstaller/UninstallerForm.cs +++ b/Spore ModAPI Easy Uninstaller/UninstallerForm.cs @@ -31,7 +31,7 @@ public UninstallerForm() this.btnCancel.Text = Strings.Cancel; this.btnUninstall.Text = Strings.UninstallSelected + " (0)"; this.btnUninstall.Enabled = false; - this.label2.Text = "Spore ModAPI Launcher Kit Version " + UpdateManager.CurrentVersion.ToString() + "\nDLLs Build " + UpdateManager.CurrentDllsBuild; + this.label2.Text = $"Spore {SupportInfo.GameFullVersionInfoString}\nLauncher Kit version {SupportInfo.LauncherKitVersionString}\nModAPI DLLs version {SupportInfo.ModAPIDllsVersionString}"; this.BringToFront(); } @@ -229,7 +229,7 @@ private void ExecuteConfigurator(ModConfiguration mod) } catch (Exception ex) { - MessageBox.Show(this, ex.Message, CommonStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show(this, ex.Message, "Easy Uninstaller Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } diff --git a/Spore ModAPI Launcher/Program.cs b/Spore ModAPI Launcher/Program.cs index dccd545..47e897b 100644 --- a/Spore ModAPI Launcher/Program.cs +++ b/Spore ModAPI Launcher/Program.cs @@ -21,7 +21,6 @@ namespace SporeModAPI_Launcher class Program { private const string ModAPIFixDownloadURL = "https://davoonline.com/sporemodder/emd4600/SporeApp_ModAPIFix.zip"; - private const string ModApiHelpThreadURL = "https://launcherkit.sporecommunity.com/support"; private string SporebinPath; private string ExecutablePath; @@ -42,7 +41,7 @@ static void Main() if (Permissions.IsAdministrator()) { proceed = false; - if (MessageBox.Show("For security reasons, explicitly running the Spore ModAPI Launcher as Administrator (by right-clicking and selecting \"Run as Administrator\") is not recommended. Doing so will also prevent you from being able to load creations into Spore by dragging their PNGs into the game window. Are you sure you want to proceed?", String.Empty, MessageBoxButtons.YesNo) == DialogResult.Yes) + if (SupportInfo.ShowWarning(Strings.RunAsAdminWarning, String.Empty, true, false, MessageBoxButtons.YesNo) == DialogResult.Yes) proceed = true; } @@ -85,7 +84,7 @@ void Execute() // ensure we have detected a valid game version if (this.ExecutableType == GameVersionType.None) { - MessageBox.Show(Strings.UnsupportedSporeVersion, Strings.UnsupportedSporeVersionTitle); + SupportInfo.ShowError(Strings.UnsupportedSporeVersion, Strings.UnsupportedSporeVersionTitle); return; } @@ -100,7 +99,7 @@ void Execute() if (fileVersionInfo.InternalName == originalFileName || fileVersionInfo.OriginalFilename == originalFileName) { - MessageBox.Show(Strings.SporeModLoaderDetected.Replace("$PATH$", sporeModLoaderPath), Strings.SporeModLoaderDetectedTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + SupportInfo.ShowWarning(Strings.SporeModLoaderDetected.Replace("$PATH$", sporeModLoaderPath), Strings.SporeModLoaderDetectedTitle); return; } } @@ -110,6 +109,12 @@ void Execute() // ignore exception, SporeModLoader is likely not installed } + // Check if LAA was used on incompatible game version + if(!GameVersion.IsLAACompatible(this.ExecutableType) && LAAUtils.IsLAA(this.ExecutablePath)) + { + SupportInfo.ShowWarning(Strings.LAAUnsupported, Strings.LAAUnsupportedTitle); + } + // get the correct executable path this.ExecutablePath = Path.Combine(this.SporebinPath, GameVersion.GetExecutableFileName(this.ExecutableType)); if (!File.Exists(this.ExecutablePath)) @@ -127,7 +132,7 @@ void Execute() throw new Exception(CommonStrings.GameNotFound); } } - + // we must also check if the steam_api.dll doesn't exist (it's required for Origin users) if (GameVersion.RequiresModAPIFix(this.ExecutableType) && !File.Exists(Path.Combine(this.SporebinPath, "steam_api.dll"))) @@ -150,7 +155,7 @@ void Execute() } catch { - MessageBox.Show(Strings.CannotApplySteamFix.Replace("$PATH$", this.SporebinPath), Strings.CannotApplySteamFixTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); + SupportInfo.ShowWarning(Strings.CompatibilityFixErrorSteam.Replace("$PATH$", this.SporebinPath), Strings.CompatibilityFixErrorTitle); } } } @@ -162,15 +167,10 @@ void Execute() ShowError(ex); } } - + public void ShowError(Exception ex) { - string versionInfo = "Launcher Kit version: " + UpdateManager.CurrentVersion + "\nModAPI DLLs version: " + UpdateManager.CurrentDllsBuild + "\nLauncher Kit path: " + Assembly.GetEntryAssembly().Location; - if (this.ExecutablePath != null && File.Exists(this.ExecutablePath)) - { - versionInfo += "\n\nSpore version: " + FileVersionInfo.GetVersionInfo(this.ExecutablePath).FileVersion + " - " + this.ExecutableType + "\nSpore path: " + this.ExecutablePath; - } - MessageBox.Show(Strings.GalacticAdventuresNotExecuted + "\n" + ModApiHelpThreadURL + "\n\n" + ex.GetType() + "\n\n" + ex.Message + "\n\n" + ex.StackTrace + "\n\n" + versionInfo, CommonStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + SupportInfo.ShowError(Strings.GameNotExecuted + "\n\n" + ex.GetType() + "\n" + ex.Message + "\n\n" + ex.StackTrace, Strings.LauncherError); if (ex is Win32Exception) { var exc = ex as Win32Exception; @@ -191,12 +191,12 @@ string[] GetDLLsToInject(string dllEnding) //coreLibs and mLibs string coreLibsPath = Path.Combine(this.LauncherKitPath, "coreLibs"); - string mLibsPath = Path.Combine(this.LauncherKitPath, "mLibs"); + string mLibsPath = Path.Combine(this.LauncherKitPath, "mLibs"); if (!Directory.Exists(mLibsPath)) Directory.CreateDirectory(mLibsPath); - const string coreLibFile = "SporeModAPI.lib"; + const string coreLibFile = "SporeModAPI.lib"; string coreLibPath = Path.Combine(coreLibsPath, coreLibFile); string coreLegacyDllName = "SporeModAPI-" + dllEnding + ".dll"; string coreLegacyDllPath = Path.Combine(this.LauncherKitPath, coreLegacyDllName); @@ -219,7 +219,7 @@ string[] GetDLLsToInject(string dllEnding) dlls.Add(coreLegacyDllPath); foreach (string file in Directory.EnumerateFiles(mLibsPath) - .Where(x => + .Where(x => { x = x.ToLowerInvariant(); return x.EndsWith(".dll") && x != coreDllOutPath.ToLowerInvariant(); @@ -294,7 +294,7 @@ void CreateSporeProcess() } if (!NativeMethods.CreateProcess(null, "\"" + this.ExecutablePath + "\" " + sb, - IntPtr.Zero, IntPtr.Zero, false, NativeTypes.ProcessCreationFlags.CREATE_SUSPENDED, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero, false, NativeTypes.ProcessCreationFlags.CREATE_SUSPENDED, IntPtr.Zero, this.SporebinPath, ref this.StartupInfo, out this.ProcessInfo)) { ThrowWin32Exception(Strings.ProcessNotStarted); @@ -311,7 +311,7 @@ void ResumeSporeProcess() bool HandleOriginUsers() { - if (MessageBox.Show(Strings.DownloadOriginFix, Strings.FileNeeded, MessageBoxButtons.OKCancel) == DialogResult.OK) + if (SupportInfo.ShowInfo(Strings.CompatibilityFixDownload, Strings.CompatibilityFixDownloadTitle, true, false, MessageBoxButtons.YesNo) == DialogResult.Yes) { return ShowDownloadFixDialog(this.SporebinPath); } @@ -330,7 +330,7 @@ static bool ShowDownloadFixDialog(string outputPath) Thread thread = new Thread(() => { - var dialog = new ProgressDialog(Strings.DownloadFixTitle, Strings.DownloadFixTitle, (s, e) => + var dialog = new ProgressDialog(Strings.CompatibilityFixDownloadTitle, Strings.CompatibilityFixDownloadTitle, (s, e) => { try { @@ -351,7 +351,7 @@ static bool ShowDownloadFixDialog(string outputPath) } catch (Exception ex) { - MessageBox.Show(ex.ToString(), "Failed to download or extract ModAPI Fix"); + SupportInfo.ShowError(Strings.CompatibilityFixErrorEA + "\n\n" + ex.ToString(), Strings.CompatibilityFixErrorTitle); result = false; } }); @@ -382,7 +382,7 @@ static bool ExtractFixFiles(MemoryStream zipStream, string outputPath) } catch (System.UnauthorizedAccessException) { - MessageBox.Show(CommonStrings.UnauthorizedAccess, CommonStrings.UnauthorizedAccessTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); + SupportInfo.ShowError(Strings.CompatibilityFixErrorEA, Strings.CompatibilityFixErrorTitle); return false; } } diff --git a/Spore ModAPI Launcher/Strings.resx b/Spore ModAPI Launcher/Strings.resx index daef9d6..75a0bed 100644 --- a/Spore ModAPI Launcher/Strings.resx +++ b/Spore ModAPI Launcher/Strings.resx @@ -117,17 +117,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Downloading ModAPI Fix + + Downloading compatibility fix - - EA App and Origin users need to download an additional file in order for mods to work. If you press OK, the launcher will download and apply this additional file. + + The Launcher Kit needs to download a compatibility fix for your current game version. Do you want to continue? - - File needed - - - Something went wrong while starting or injecting your mods into Spore. The game may start, but some mods will not work correctly. Please report the issue, along with a screenshot of this error, using the instructions at the following website: + + Something went wrong while starting or loading your mods into Spore. The game may start, but some mods will not work correctly. + +Please see https://launcherkit.sporecommunity.com/support for further help. Process thread could not be resumed. @@ -135,30 +134,65 @@ Could not start process. - - Could not apply the Steam fix. To apply it, you can do one of these two options: - - Re-execute the Launcher with Administrator Privileges. + + The Launcher Kit could not apply a Steam compatibility fix. To apply it, you can do one of these two options: + - Right click the Spore ModAPI Launcher and "Run as Administrator" once, to allow the fix to be applied, then exit the game and run it normally or - Create a file called 'steam_appid.txt', with the numbers '17390' inside, in the folder $PATH$ - - Could not apply the Steam fix + + Could not apply compatibility fix - SporeModLoader has been detected, running the Launcher Kit alongside SporeModLoader is unsupported. + SporeModLoader has been detected. Running the Launcher Kit alongside SporeModLoader is unsupported. You have the the following options: - Remove SporeModLoader to use the Launcher Kit by removing $PATH$ -- Use SporeModLoader instead of the Launcher Kit - +- Use SporeModLoader instead of the Launcher Kit SporeModLoader detected - Your current Spore game version is not compatible with this Launcher Kit version. If you downloaded the game from EA App, Steam, or GOG, please update to version 3.1.0.29 to proceed. If you're using a higher version of Spore, please see https://launcherkit.sporecommunity.com/support. + Your current Spore game version is not compatible with this Launcher Kit version. + +Both Spore and Spore Galactic Adventures must be installed from disc, EA App, Steam, or GOG. + +If you downloaded the game from EA App, Steam, or GOG, please ensure the game is up-to-date. + +Please see https://launcherkit.sporecommunity.com/support for further help. Unsupported Spore version + + Running the Spore ModAPI Launcher as Administrator (by right-clicking and selecting "Run as Administrator") is not recommended. + +This can be a security risk and can also cause problems with game functionality. You may be unable to load creations into Spore by dragging their PNGs into the game window. + +Are you sure you want to continue? + + + Spore ModAPI Launcher Error + + + The Launcher Kit could not apply an EA App compatibility fix. Please try the following: +- Ensure you have a stable internet connection + - Right click the Spore ModAPI Launcher and "Run as Administrator" once, to allow the fix to be applied, then exit the game and run it normally + + + Your current game version is not compatible with the LAA/4GB patch. The game may not launch. + +You can do one of the following: +- Downgrade to a game version that is compatible with the LAA/4GB patch +- Remove the LAA/4GB patch using the same tool used to apply it +- Remove the LAA/4GB patch by verifying the game files for Spore Galactic Adventures + +The LAA/4GB patch is not required to use mods, but may help improve game stability. + +Please see https://launcherkit.sporecommunity.com/support for further help. + + + LAA/4GB Patch Incompatible + \ No newline at end of file diff --git a/Spore ModAPI Launcher/Strings1.Designer.cs b/Spore ModAPI Launcher/Strings1.Designer.cs index e4dd869..f90b0c2 100644 --- a/Spore ModAPI Launcher/Strings1.Designer.cs +++ b/Spore ModAPI Launcher/Strings1.Designer.cs @@ -19,7 +19,7 @@ namespace SporeModAPI_Launcher { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Strings { @@ -61,59 +61,99 @@ internal Strings() { } /// - /// Looks up a localized string similar to Could not apply the Steam fix. To apply it, you can do one of these two options: - /// - Re-execute the Launcher with Administrator Privileges. + /// Looks up a localized string similar to The Launcher Kit needs to download a compatibility fix for your current game version. Do you want to continue?. + /// + public static string CompatibilityFixDownload { + get { + return ResourceManager.GetString("CompatibilityFixDownload", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloading compatibility fix. + /// + public static string CompatibilityFixDownloadTitle { + get { + return ResourceManager.GetString("CompatibilityFixDownloadTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Launcher Kit could not apply an EA App compatibility fix. Please try the following: + ///- Ensure you have a stable internet connection + /// - Right click the Spore ModAPI Launcher and "Run as Administrator" once, to allow the fix to be applied, then exit the game and run it normally. + /// + public static string CompatibilityFixErrorEA { + get { + return ResourceManager.GetString("CompatibilityFixErrorEA", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Launcher Kit could not apply a Steam compatibility fix. To apply it, you can do one of these two options: + /// - Right click the Spore ModAPI Launcher and "Run as Administrator" once, to allow the fix to be applied, then exit the game and run it normally ///or /// - Create a file called 'steam_appid.txt', with the numbers '17390' inside, in the folder $PATH$. /// - public static string CannotApplySteamFix { + public static string CompatibilityFixErrorSteam { get { - return ResourceManager.GetString("CannotApplySteamFix", resourceCulture); + return ResourceManager.GetString("CompatibilityFixErrorSteam", resourceCulture); } } /// - /// Looks up a localized string similar to Could not apply the Steam fix. + /// Looks up a localized string similar to Could not apply compatibility fix. /// - public static string CannotApplySteamFixTitle { + public static string CompatibilityFixErrorTitle { get { - return ResourceManager.GetString("CannotApplySteamFixTitle", resourceCulture); + return ResourceManager.GetString("CompatibilityFixErrorTitle", resourceCulture); } } /// - /// Looks up a localized string similar to Downloading ModAPI Fix. + /// Looks up a localized string similar to Something went wrong while starting or loading your mods into Spore. The game may start, but some mods will not work correctly. + /// + ///Please see https://launcherkit.sporecommunity.com/support for further help.. /// - public static string DownloadFixTitle { + public static string GameNotExecuted { get { - return ResourceManager.GetString("DownloadFixTitle", resourceCulture); + return ResourceManager.GetString("GameNotExecuted", resourceCulture); } } /// - /// Looks up a localized string similar to EA App and Origin users need to download an additional file in order for mods to work. If you press OK, the launcher will download and apply this additional file.. + /// Looks up a localized string similar to Your current game version is not compatible with the LAA/4GB patch. The game may not launch. + /// + ///You can do one of the following: + ///- Downgrade to a game version that is compatible with the LAA/4GB patch + ///- Remove the LAA/4GB patch using the same tool used to apply it + ///- Remove the LAA/4GB patch by verifying the game files for Spore Galactic Adventures + /// + ///The LAA/4GB patch is not required to use mods, but may help improve game stability. + /// + ///Please see https://launcherkit.sporecommunity.com/support for further [rest of string was truncated]";. /// - public static string DownloadOriginFix { + public static string LAAUnsupported { get { - return ResourceManager.GetString("DownloadOriginFix", resourceCulture); + return ResourceManager.GetString("LAAUnsupported", resourceCulture); } } /// - /// Looks up a localized string similar to File needed. + /// Looks up a localized string similar to LAA/4GB Patch Incompatible. /// - public static string FileNeeded { + public static string LAAUnsupportedTitle { get { - return ResourceManager.GetString("FileNeeded", resourceCulture); + return ResourceManager.GetString("LAAUnsupportedTitle", resourceCulture); } } /// - /// Looks up a localized string similar to Something went wrong while starting or injecting your mods into Spore. The game may start, but some mods will not work correctly. Please report the issue, along with a screenshot of this error, using the instructions at the following website:. + /// Looks up a localized string similar to Spore ModAPI Launcher Error. /// - public static string GalacticAdventuresNotExecuted { + public static string LauncherError { get { - return ResourceManager.GetString("GalacticAdventuresNotExecuted", resourceCulture); + return ResourceManager.GetString("LauncherError", resourceCulture); } } @@ -136,12 +176,24 @@ public static string ProcessNotStarted { } /// - /// Looks up a localized string similar to SporeModLoader has been detected, running the Launcher Kit alongside SporeModLoader is unsupported. + /// Looks up a localized string similar to Running the Spore ModAPI Launcher as Administrator (by right-clicking and selecting "Run as Administrator") is not recommended. + /// + ///This can be a security risk and can also cause problems with game functionality. You may be unable to load creations into Spore by dragging their PNGs into the game window. + /// + ///Are you sure you want to continue?. + /// + public static string RunAsAdminWarning { + get { + return ResourceManager.GetString("RunAsAdminWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SporeModLoader has been detected. Running the Launcher Kit alongside SporeModLoader is unsupported. /// ///You have the the following options: ///- Remove SporeModLoader to use the Launcher Kit by removing $PATH$ - ///- Use SporeModLoader instead of the Launcher Kit - ///. + ///- Use SporeModLoader instead of the Launcher Kit. /// public static string SporeModLoaderDetected { get { @@ -159,7 +211,13 @@ public static string SporeModLoaderDetectedTitle { } /// - /// Looks up a localized string similar to Your current Spore game version is not compatible with this Launcher Kit version. If you downloaded the game from EA App, Steam, or GOG, please update to version 3.1.0.29 to proceed. If you're using a higher version of Spore, please see https://launcherkit.sporecommunity.com/support.. + /// Looks up a localized string similar to Your current Spore game version is not compatible with this Launcher Kit version. + /// + ///Both Spore and Spore Galactic Adventures must be installed from disc, EA App, Steam, or GOG. + /// + ///If you downloaded the game from EA App, Steam, or GOG, please ensure the game is up-to-date. + /// + ///Please see https://launcherkit.sporecommunity.com/support for further help.. /// public static string UnsupportedSporeVersion { get { From 3caadb5d803eeb5c79969b9a73bed2c530932b2c Mon Sep 17 00:00:00 2001 From: Kade Date: Wed, 6 May 2026 23:12:01 -0400 Subject: [PATCH 4/9] Common/Installer: Warn for blocked updates, force update check when mods need it, generate support info file --- ModAPI.Common/CommonStrings.Designer.cs | 31 +++++++++ ModAPI.Common/CommonStrings.resx | 13 ++++ ModAPI.Common/SupportInfo.cs | 27 +++++++- ModAPI.Common/Update/UpdateManager.cs | 67 +++++++++++++++---- Spore ModAPI Easy Installer/EasyInstaller.cs | 1 + .../Strings.Designer.cs | 4 +- Spore ModAPI Easy Installer/Strings.resx | 4 +- .../XmlInstallerWindow.xaml.cs | 4 ++ 8 files changed, 133 insertions(+), 18 deletions(-) diff --git a/ModAPI.Common/CommonStrings.Designer.cs b/ModAPI.Common/CommonStrings.Designer.cs index 8a4de4e..0fab14f 100644 --- a/ModAPI.Common/CommonStrings.Designer.cs +++ b/ModAPI.Common/CommonStrings.Designer.cs @@ -176,6 +176,28 @@ public static string SteamDownloadedButNotLaunchedTitle { } } + /// + /// Looks up a localized string similar to The Launcher Kit has been configured to skip checking for updates. You may be missing new features and bug fixes, and may be unable to use newer mods. + /// + ///Outdated versions of the Launcher Kit are unsupported. If you cannot use automatic updates, for example, because this computer does not have an internet connection, please periodically check https://launcherkit.sporecommunity.com for the latest updates. + /// + ///Would you like to automatically check for updates?. + /// + public static string UpdateCheckDisabledNotice { + get { + return ResourceManager.GetString("UpdateCheckDisabledNotice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launcher Kit Updates Disabled. + /// + public static string UpdateCheckDisabledNoticeTitle { + get { + return ResourceManager.GetString("UpdateCheckDisabledNoticeTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The Launcher Kit could not connect to the update service. Try again in a few minutes, or check https://launcherkit.sporecommunity.com/support for help.. /// @@ -194,6 +216,15 @@ public static string UpdateCheckFailedTitle { } } + /// + /// Looks up a localized string similar to The Launcher Kit must be updated manually. Please visit https://launcherkit.sporecommunity.com/support for more information.. + /// + public static string UpdateUnrecognized { + get { + return ResourceManager.GetString("UpdateUnrecognized", resourceCulture); + } + } + /// /// Looks up a localized string similar to ModAPI DLLs are updating to version . /// diff --git a/ModAPI.Common/CommonStrings.resx b/ModAPI.Common/CommonStrings.resx index 1a99a9b..13c92e1 100644 --- a/ModAPI.Common/CommonStrings.resx +++ b/ModAPI.Common/CommonStrings.resx @@ -167,12 +167,25 @@ If you continue to get this message, or the game is not installed from Steam, th Steam installation not completed + + The Launcher Kit has been configured to skip checking for updates. You may be missing new features and bug fixes, and may be unable to use newer mods. + +Outdated versions of the Launcher Kit are unsupported. If you cannot use automatic updates, for example, because this computer does not have an internet connection, please periodically check https://launcherkit.sporecommunity.com for the latest updates. + +Would you like to automatically check for updates? + + + Launcher Kit Updates Disabled + The Launcher Kit could not connect to the update service. Try again in a few minutes, or check https://launcherkit.sporecommunity.com/support for help. Launcher Kit Update + + The Launcher Kit must be updated manually. Please visit https://launcherkit.sporecommunity.com/support for more information. + ModAPI DLLs are updating to version diff --git a/ModAPI.Common/SupportInfo.cs b/ModAPI.Common/SupportInfo.cs index 7975fc5..603156d 100644 --- a/ModAPI.Common/SupportInfo.cs +++ b/ModAPI.Common/SupportInfo.cs @@ -34,7 +34,7 @@ public static class SupportInfo /// By default, this is "%programdata%\Spore ModAPI Launcher Kit". /// The path will NOT have a trailing slash. /// - public static String LauncherKitPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + public static string LauncherKitPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -120,7 +120,7 @@ private static DialogResult ShowMessageBox(string message, string title, bool sh } // Append LK info - message += $"\nLauncher Kit version: {LauncherKitVersionString}\nModAPI DLLs Version: {ModAPIDllsVersionString}"; + message += $"\nLauncher Kit version: {LauncherKitVersionString}\nModAPI DLLs version: {ModAPIDllsVersionString}"; if (showPaths) { message += $"\nLauncher Kit path: {LauncherKitPath}"; @@ -156,5 +156,28 @@ public static DialogResult ShowError(string message, string title, bool showGame return ShowMessageBox(message, title, showGameInfo, showPaths, buttons, MessageBoxIcon.Error); } + + + /// + /// Writes a text file containing support information. + /// This includes the game version, game paths, Launcher Kit version, ModAPI DLLs version, and Launcher Kit path. + /// + public static void WriteSupportInfoFile(string path) + { + string[] text = + { + $"Spore version: {GameFullVersionInfoString}", + $"Spore path: {SporePath.GetSporebinEP1Path()}", + $"Spore Core data path: {SporePath.GetDataPath(SporePath.Game.Spore)}", + $"Spore C&C data path: {SporePath.GetDataPath(SporePath.Game.CreepyAndCute)}", + $"Spore GA data path: {SporePath.GetDataPath(SporePath.Game.GalacticAdventures)}", + $"Launcher Kit version: {LauncherKitVersionString}", + $"ModAPI DLLs version: {ModAPIDllsVersionString}", + $"Launcher Kit path: {LauncherKitPath}", + }; + + File.WriteAllLines(path, text); + } + } } \ No newline at end of file diff --git a/ModAPI.Common/Update/UpdateManager.cs b/ModAPI.Common/Update/UpdateManager.cs index 28cac88..14b1e8e 100644 --- a/ModAPI.Common/Update/UpdateManager.cs +++ b/ModAPI.Common/Update/UpdateManager.cs @@ -47,28 +47,34 @@ public static Version CurrentDllsBuild if (versions.Count() > 0) { Version minVer = versions.Min(); - return new Version(minVer.Major, minVer.Minor, minVer.Build, 0); + return new Version(minVer.Major, minVer.Minor, minVer.Build, 0); // revision must be 0 for update checking purposes, as we use revision to indicate whether the DLL is for disc/download/combined versions } else return new Version(999, 999, 999, 999); } } + public static bool IsUpdateCheckDisabled => File.Exists(UpdaterBlockPath); public static void CheckForUpdates() { + // Create appdata folder if it doesn't exist + Directory.CreateDirectory(AppDataPath); + + // Write support info file to appdata folder + SupportInfo.WriteSupportInfoFile(Path.Combine(AppDataPath, "support.info")); + + // Make sure game is not running before running any Launcher Kit apps if ((Process.GetProcessesByName("SporeApp").Length > 0) || (Process.GetProcessesByName("SporeApp_ModAPIFix").Length > 0)) { SupportInfo.ShowWarning(CommonStrings.GameAlreadyRunning, CommonStrings.GameAlreadyRunningTitle, true, false); Process.GetCurrentProcess().Kill(); } - if (!Directory.Exists(AppDataPath)) - Directory.CreateDirectory(AppDataPath); - - File.WriteAllText(Path.Combine(AppDataPath, "path.info"), Directory.GetParent(Assembly.GetEntryAssembly().Location).ToString()); - + // Delete old updater exe if present if (File.Exists(UpdaterDestPath)) + { File.Delete(UpdaterDestPath); + } if (File.Exists(LastUpdateCheckTimePath)) { @@ -79,20 +85,35 @@ public static void CheckForUpdates() LastUpdateDateTimeFormat, CultureInfo.InvariantCulture); + // If it's been less than an hour since last update check, don't check again if ((DateTime.Now - lastUpdateCheckDateTime).TotalHours < 1) { return; } + + // If update check is disabled, and it's been more than 30 days since last update check, prompt user to check for updates + if (IsUpdateCheckDisabled && (DateTime.Now - lastUpdateCheckDateTime).TotalDays >= 30) + { + PromptUserUnblockUpdates(); + } } catch (Exception) { - File.Delete(LastUpdateCheckTimePath); + ResetLastUpdateCheckTime(); } } + else if (IsUpdateCheckDisabled) + { + // If update check is disabled, and this is a new install or we force checked for updates, prompt user to check for updates + PromptUserUnblockUpdates(); + } - if (File.Exists(UpdaterBlockPath)) + // Record current time as last update check time + File.WriteAllText(LastUpdateCheckTimePath, DateTime.Now.ToString(LastUpdateDateTimeFormat)); + + // Don't check for updates when block file exists + if (IsUpdateCheckDisabled) { - // don't check for updates when block file exists return; } @@ -254,8 +275,6 @@ public static void CheckForUpdates() dialog.ShowDialog(); } } - - File.WriteAllText(LastUpdateCheckTimePath, DateTime.Now.ToString(LastUpdateDateTimeFormat)); } catch (Exception ex) { @@ -288,7 +307,31 @@ static void ShowUpdateCheckFailedMessage(List exceptions) static void ShowUnrecognizedUpdateInfoVersionMessage() { - SupportInfo.ShowInfo("This update to the Spore ModAPI Launcher Kit must be downloaded manually. Please visit https://launcherkit.sporecommunity.com/support for more information.", CommonStrings.UpdateCheckFailedTitle, false, true); + SupportInfo.ShowInfo(CommonStrings.UpdateUnrecognized, CommonStrings.UpdateCheckFailedTitle, false, true); + } + + /// + /// Forces the Launcher Kit to check for updates the next time it is run, skipping the one hour timeout. + /// If updates are blocked, this will also prompt the user to unblock updates on next launch. + /// + public static void ResetLastUpdateCheckTime() + { + File.Delete(LastUpdateCheckTimePath); } + + /// + /// Warns the user that update checks are disabled, and prompts them to re-enable. + /// Returns Yes or No depending on whether the user wants to re-enable update checks. If Yes, the update block file is removed. + /// + static DialogResult PromptUserUnblockUpdates() + { + var result = SupportInfo.ShowInfo(CommonStrings.UpdateCheckDisabledNotice, CommonStrings.UpdateCheckDisabledNoticeTitle, false, false, MessageBoxButtons.YesNo); + if (result == DialogResult.Yes) + { + File.Delete(UpdaterBlockPath); + } + return result; + } + } } \ No newline at end of file diff --git a/Spore ModAPI Easy Installer/EasyInstaller.cs b/Spore ModAPI Easy Installer/EasyInstaller.cs index e23e639..00e2bd3 100755 --- a/Spore ModAPI Easy Installer/EasyInstaller.cs +++ b/Spore ModAPI Easy Installer/EasyInstaller.cs @@ -400,6 +400,7 @@ static ResultType TryExecuteInstaller(string inputFile, string modName) if (modCoreDllsVersion != null && modCoreDllsVersion > UpdateManager.CurrentDllsBuild) { SupportInfo.ShowWarning(Strings.OutdatedDllVersion.Replace("$MODNAME$", modName).Replace("$REQUIREDVERSION$", modCoreDllsVersion.ToString()), Strings.OutdatedDllVersionTitle, false, false); + UpdateManager.ResetLastUpdateCheckTime(); return ResultType.ModNotInstalled; } // If the version is not specified, continue installing (the value is optional because not all mods use the ModAPI SDK) diff --git a/Spore ModAPI Easy Installer/Strings.Designer.cs b/Spore ModAPI Easy Installer/Strings.Designer.cs index 3c63b6a..f460878 100644 --- a/Spore ModAPI Easy Installer/Strings.Designer.cs +++ b/Spore ModAPI Easy Installer/Strings.Designer.cs @@ -133,7 +133,7 @@ internal static string InstallingModTitle { } /// - /// Looks up a localized string similar to cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help.. + /// Looks up a localized string similar to "$MODNAME$" cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help.. /// internal static string InvalidDllVersion { get { @@ -178,7 +178,7 @@ internal static string ModNotInstalled { } /// - /// Looks up a localized string similar to cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. + /// Looks up a localized string similar to "$MODNAME$" cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. /// ///Required ModAPI DLLs version: $REQUIREDVERSION$ or higher. /// diff --git a/Spore ModAPI Easy Installer/Strings.resx b/Spore ModAPI Easy Installer/Strings.resx index 729025c..9a98e2a 100644 --- a/Spore ModAPI Easy Installer/Strings.resx +++ b/Spore ModAPI Easy Installer/Strings.resx @@ -157,12 +157,12 @@ Installation completed - cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. + "$MODNAME$" cannot be installed, because it requires a newer version of the ModAPI DLLs. Please restart the launcher and allow it to update. Required ModAPI DLLs version: $REQUIREDVERSION$ or higher - cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help. + "$MODNAME$" cannot be installed, because it specifies an invalid version of the ModAPI DLLs. Please ask the mod developer for help. The Easy Installer does not currently have permission to install or change mods. You may need to right-click the Easy Installer and "Run as Administrator". diff --git a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs index 3b15576..57f4d3c 100644 --- a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs +++ b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs @@ -198,7 +198,10 @@ private void PrepareInstaller() if ((Document.SelectSingleNode("/mod").Attributes["dllsBuild"] != null) && (Version.TryParse(Document.SelectSingleNode("/mod").Attributes["dllsBuild"].Value + ".0", out Version minFeaturesLevel))) { if (minFeaturesLevel > UpdateManager.CurrentDllsBuild) + { + UpdateManager.ResetLastUpdateCheckTime(); CyclePage(0, 2); + } } else throw new Exception("This Mod has not specified a valid minimum features level. Please inform the mod's developer of this."); @@ -283,6 +286,7 @@ private void PrepareInstaller() } else { + UpdateManager.ResetLastUpdateCheckTime(); CyclePage(0, 2); } } From dbfbf13134fa94de48dd3079ba6792c43c3da29e Mon Sep 17 00:00:00 2001 From: Kade Date: Thu, 7 May 2026 00:00:03 -0400 Subject: [PATCH 5/9] Common: Show warning if Steam set wrong data paths, fixes #26 --- ModAPI.Common/SporePath.cs | 68 ++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/ModAPI.Common/SporePath.cs b/ModAPI.Common/SporePath.cs index c7527b9..bc454e4 100644 --- a/ModAPI.Common/SporePath.cs +++ b/ModAPI.Common/SporePath.cs @@ -105,6 +105,37 @@ public static bool SporeIsInstalledOnSteam() return result != null && ((int)result != 0); } + /// + /// Returns true if the game is installed such that all data folders share the same parent folder. + /// Usually this doesn't matter, but as of early 2026, Steam may install multiple copies of the game due to differing app IDs. This can result in mods being installed to the wrong copy, so this function checks if this is the case. + /// + private static bool IsDataDirsSameParent() + { + // Get the data paths + var sporeDataPath = GetDataPath(Game.Spore); + var ccDataPath = GetDataPath(Game.CreepyAndCute); // null if not installed + var gaDataPath = GetDataPath(Game.GalacticAdventures); + + // Get parent folder of each data path + var sporeParent = sporeDataPath != null ? Directory.GetParent(sporeDataPath).FullName : null; + var ccParent = ccDataPath != null ? Directory.GetParent(ccDataPath).FullName : null; + var gaParent = gaDataPath != null ? Directory.GetParent(gaDataPath).FullName : null; + + // Make sure Spore and GA share the same parent folder + if (sporeParent != gaParent) + { + return false; + } + + // If CC is installed, it should also share the same parent + if (ccParent != null && sporeParent != ccParent) + { + return false; + } + + return true; + } + /// /// Returns true if Spore and GA are properly installed. /// If either game is not properly installed, returns false, and optionally shows an error message to the user. If the game is installed from Steam but hasn't been launched to complete the installation, shows a specific error message about launching the game on Steam. @@ -112,24 +143,39 @@ public static bool SporeIsInstalledOnSteam() /// public static bool IsGameInstalled(bool showMessageWhenFalse) { - if (IsInstalled(Game.Spore) && IsInstalled(Game.GalacticAdventures)) + // Make sure data dirs are present for Spore and GA + if (!IsInstalled(Game.Spore) || !IsInstalled(Game.GalacticAdventures)) { - return true; + if (showMessageWhenFalse) + { + // Steam doesn't run the install script to create the registry keys until the game is launched from Steam for the first time + if (SporeIsInstalledOnSteam()) + { + SupportInfo.ShowInfo(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle, true, false); + } + else + { + SupportInfo.ShowError(CommonStrings.GameNotFound, CommonStrings.GameNotFoundTitle, false, false); + } + } + return false; } - if (showMessageWhenFalse) + // For Steam only, check that all data dirs are in the same parent folder + // As of early 2026, Steam may install multiple copies of the game due to differing app IDs. This can result in mods being installed to the wrong copy. + // The "current" copy (Spore vs C&C vs GA) used is the most recent one launched from Steam. If we detect mixed folders, it means that non-GA has been launched, and the user should be told to launch GA to force everything to use GA's copy of the data. + // Prior to early 2026, Steam only installed one copy, and they were all in the same folder, so this check will still pass. + // NOTE: This may need to be changed in the future, if Steam changes anything again. + if (SporeIsInstalledOnSteam() && !IsDataDirsSameParent()) { - // Steam doesn't run the install script to create the registry keys until the game is launched from Steam for the first time - if (SporeIsInstalledOnSteam()) - { - SupportInfo.ShowInfo(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle, true, false); - } - else + if (showMessageWhenFalse) { - SupportInfo.ShowError(CommonStrings.GameNotFound, CommonStrings.GameNotFoundTitle, false, false); + SupportInfo.ShowWarning(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle); } + return false; } - return false; + + return true; } /// From 118d376c5ed76a9aede9f7682c779ee979a64779 Mon Sep 17 00:00:00 2001 From: Kade Date: Thu, 7 May 2026 01:20:18 -0400 Subject: [PATCH 6/9] Common: fix LAA check for EA/Origin --- ModAPI.Common/SupportInfo.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ModAPI.Common/SupportInfo.cs b/ModAPI.Common/SupportInfo.cs index 603156d..45ab50b 100644 --- a/ModAPI.Common/SupportInfo.cs +++ b/ModAPI.Common/SupportInfo.cs @@ -96,8 +96,15 @@ public static string GameFullVersionInfoString return "Game not found"; } - var sporeAppPath = Path.Combine(SporePath.GetSporebinEP1Path(), "SporeApp.exe"); - var laaString = LAAUtils.IsLAA(sporeAppPath) ? ", LAA" : ""; + // Check if LAA + var laaString = ""; + var versionType = GameVersion.DetectVersion(Path.Combine(SporePath.GetSporebinEP1Path(), "SporeApp.exe")); + var exeName = GameVersion.GetExecutableFileName(versionType); + if (exeName != null) + { + var sporeAppPath = Path.Combine(SporePath.GetSporebinEP1Path(), exeName); + laaString = LAAUtils.IsLAA(sporeAppPath) ? ", LAA" : ""; + } return $"{GameVersionString} - {GameVersionTypeString}{laaString}"; } From 6a2e2a596bcda68aac53b1994d599768c7ac3a23 Mon Sep 17 00:00:00 2001 From: Rosalie Wanders Date: Fri, 8 May 2026 01:34:15 +0200 Subject: [PATCH 7/9] Spore ModAPI Launcher: change SporeModLoader warning into an error --- Spore ModAPI Launcher/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Spore ModAPI Launcher/Program.cs b/Spore ModAPI Launcher/Program.cs index 47e897b..056b71d 100644 --- a/Spore ModAPI Launcher/Program.cs +++ b/Spore ModAPI Launcher/Program.cs @@ -99,7 +99,7 @@ void Execute() if (fileVersionInfo.InternalName == originalFileName || fileVersionInfo.OriginalFilename == originalFileName) { - SupportInfo.ShowWarning(Strings.SporeModLoaderDetected.Replace("$PATH$", sporeModLoaderPath), Strings.SporeModLoaderDetectedTitle); + SupportInfo.ShowError(Strings.SporeModLoaderDetected.Replace("$PATH$", sporeModLoaderPath), Strings.SporeModLoaderDetectedTitle); return; } } From 1e7e2b4d156cbf47a6fee85b50088ad521be2b46 Mon Sep 17 00:00:00 2001 From: Kade Date: Thu, 7 May 2026 19:57:48 -0400 Subject: [PATCH 8/9] Common: small optimization for getting sporebin path --- ModAPI.Common/SporePath.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ModAPI.Common/SporePath.cs b/ModAPI.Common/SporePath.cs index bc454e4..a2226dc 100644 --- a/ModAPI.Common/SporePath.cs +++ b/ModAPI.Common/SporePath.cs @@ -1,6 +1,5 @@ using Microsoft.Win32; using System.IO; -using System.Windows; namespace ModAPI.Common { @@ -19,6 +18,8 @@ public enum Game public static readonly string SteamAppsKey = @"HKEY_CURRENT_USER\Software\Valve\Steam\Apps\"; public static readonly string GalacticAdventuresSteamID = "24720"; + private static string _sporebinEP1Path = null; + /// /// Gets the path to the SporebinEP1 folder that contains SporeApp.exe for Galactic Adventures. /// The path will NOT have a trailing slash. @@ -26,6 +27,12 @@ public enum Game /// public static string GetSporebinEP1Path() { + // Cache the result to avoid repeated filesystem lookups + if (_sporebinEP1Path != null) + { + return _sporebinEP1Path; + } + // Use GA's data path as a starting point, as SporebinEP1 is always next to whatever GA's data dir is var dataPath = GetDataPath(Game.GalacticAdventures); if (dataPath == null) @@ -33,7 +40,7 @@ public static string GetSporebinEP1Path() return null; } - var gameRootPath = Directory.GetParent(dataPath).ToString(); + var gameRootPath = Directory.GetParent(dataPath).FullName; var binPath = Path.Combine(gameRootPath, "SporebinEP1"); // Verify that this folder actually contains SporeApp.exe @@ -42,7 +49,7 @@ public static string GetSporebinEP1Path() return null; } - return binPath; + return _sporebinEP1Path = binPath; } /// From 75d4d3140d827300f5ddf1e68d9885b420c8f4a1 Mon Sep 17 00:00:00 2001 From: Kade Date: Thu, 7 May 2026 20:41:40 -0400 Subject: [PATCH 9/9] Website: update various pages for new LK version --- docs/_common-errors/data-directory-missing.md | 6 +++--- docs/_common-errors/steam-4gb-patch-error.md | 8 ++++---- docs/_common-errors/unauthorized.md | 4 ++-- docs/_info/game-versions.md | 4 +++- docs/_info/improving-stability.md | 8 +++++--- docs/_info/uninstall-lk.md | 4 +++- docs/_info/update-lk.md | 18 +++++++++++------- docs/support.md | 11 +++++++++-- 8 files changed, 40 insertions(+), 23 deletions(-) diff --git a/docs/_common-errors/data-directory-missing.md b/docs/_common-errors/data-directory-missing.md index 78a1376..f66cbca 100644 --- a/docs/_common-errors/data-directory-missing.md +++ b/docs/_common-errors/data-directory-missing.md @@ -7,9 +7,9 @@ You may also see "Configuration Script Failed" along with this error. The two er ## Steam users Please launch Spore Galactic Adventures once from Steam before using the Launcher Kit. Afterwards, you should always use the ModAPI Launcher instead of launching from Steam. -The error happens because Steam does not finish installing games until the first time they are launched. As a result, the game cannot start because it is not fully installed, and the Launcher Kit is unable to detect that the game is installed. +Do not attempt to launch the base game or Creepy & Cute. -This also eliminates an issue where you may be prompted to choose where the game is installed when using the Launcher kit. You should not continue to receive this prompt. +The error happens because Steam does not finish installing games until the first time they are launched. As a result, the game cannot start because it is not fully installed, and the Launcher Kit is unable to detect that the game is installed. ## All other users -Please [ask for help](/support). +Please [ask for help](/support). \ No newline at end of file diff --git a/docs/_common-errors/steam-4gb-patch-error.md b/docs/_common-errors/steam-4gb-patch-error.md index db73010..877fc34 100644 --- a/docs/_common-errors/steam-4gb-patch-error.md +++ b/docs/_common-errors/steam-4gb-patch-error.md @@ -1,9 +1,9 @@ --- title: Steam Application load error after 4GB patch -description: This error may occur if you have installed the 4GB patch on the latest Steam version of the game. You can remove the 4GB patch by verifying game files in Steam. This will fix the error. Please see proper instructions on how to use the 4GB patch with Steam. +description: This error may occur if you have installed the LAA/4GB patch on the latest Steam version of the game. You can remove the LAA/4GB patch by verifying game files in Steam. This will fix the error. Please see proper instructions on how to use the LAA/4GB patch with Steam. --- -This error may occur if you have installed the 4GB patch on the latest Steam version of the game. It is caused by Steam's DRM. +This error may occur if you have installed the LAA/4GB patch on the latest Steam version of the game. It is caused by Steam's DRM. -You can remove the 4GB patch by verifying game files in Steam. This will fix the error. +You can remove the LAA/4GB patch by verifying game files in Steam. This will fix the error. -Please see [here](improving-stability) for proper instructions on how to use the 4GB patch with Steam. +Please see [here](improving-stability) for proper instructions on how to use the LAA/4GB patch with Steam. \ No newline at end of file diff --git a/docs/_common-errors/unauthorized.md b/docs/_common-errors/unauthorized.md index f7fbddd..ddb594a 100644 --- a/docs/_common-errors/unauthorized.md +++ b/docs/_common-errors/unauthorized.md @@ -1,7 +1,7 @@ --- title: Unauthorized access exception --- -This error will show +This error is known to occur when Spore is installed from EA App, and will show ``` System.UnauthorizedAccessException Access to the path '' is denied. @@ -14,4 +14,4 @@ Access to the path '' is denied. - Right click the `mLibs` folder > Properties > Security tab > Edit - Select "Users" in the list at the top - In the list at the bottom, check the "Allow" box next to "Full control" -- Click "OK" to save and close both windows +- Click "OK" to save and close both windows \ No newline at end of file diff --git a/docs/_info/game-versions.md b/docs/_info/game-versions.md index 1561b88..9f00c9c 100644 --- a/docs/_info/game-versions.md +++ b/docs/_info/game-versions.md @@ -12,7 +12,9 @@ Any other game version that is not listed here is not supported for modding. *Note: To use Spore's online features, you must have the latest game version (3.1.0.29), OR the [SporeFixOnline mod]({{ page.fixonline_mod_url }}).* ### Checking your game version -Open the folder where Spore Galactic Adventures is installed and open the `SporebinEP1` folder. Hover over `SporeApp` to see the game version. +If the Launcher Kit is installed, open the Easy Uninstaller. The game version is shown in the bottom left corner. + +Alternatively, open the folder where Spore Galactic Adventures is installed and open the `SporebinEP1` folder. Hover over `SporeApp` to see the game version. ### Do not install multiple copies of the game Installing multiple copies of the game is known to cause a variety of problems. For example, do not install the game from EA App if you already have it installed from Steam or GOG. diff --git a/docs/_info/improving-stability.md b/docs/_info/improving-stability.md index 5e91f75..59a3af3 100644 --- a/docs/_info/improving-stability.md +++ b/docs/_info/improving-stability.md @@ -13,13 +13,15 @@ This mod fixes crashes that occur in several *specific* scenarios, such as when --- -## 4GB Patch (Large Address Aware) -Spore is an older game and is not optimized to take full advantage of modern computers. This external tool patches the game to use more memory, doubling from the historical Windows default of 2GB, to 4GB (internally known as being "large address aware"). This can reduce crashes that occur if the game exceeds the normal Windows memory limitations. +## LAA/4GB Patch (Large Address Aware) +Spore is an older game and is not optimized to take full advantage of modern computers. This external tool patches the game to use more memory, doubling from the historical Windows default of 2GB, to 4GB (internally known as being "large address aware" or "LAA" for short). This can reduce crashes that occur if the game exceeds the normal Windows memory limitations. -This is not a Spore-specific patch and should not be confused with official Spore patches, such as patch 1.5.1. +This is not a Spore-specific patch and should not be confused with official Spore patches, such as patch 5.1. [Download 4GB Patch]({{ page.4gb_patch_url }}) +When correctly applied, the Easy Uninstaller will show "LAA" after the game version in the bottom left corner. + #### Disc and GOG Apply the 4GB patch to `SporeApp.exe` in the `SporebinEP1` folder. diff --git a/docs/_info/uninstall-lk.md b/docs/_info/uninstall-lk.md index a1c2d4a..e9fa1b3 100644 --- a/docs/_info/uninstall-lk.md +++ b/docs/_info/uninstall-lk.md @@ -19,7 +19,9 @@ Do not uninstall the Launcher Kit unless no mods are listed in the Easy Uninstal ## Finding and removing Launcher Kit You will need to know where the Launcher Kit is installed to. By default, this is `%programdata%\Spore ModAPI Launcher Kit`, but you may have changed it during installation. -If you cannot find the Launcher Kit files, press Win+R and type in `notepad "%appdata%\Spore ModAPI Launcher\path.info"`. A notepad window will open, containing the location of the Launcher Kit files. +If you cannot find the Launcher Kit files, press Win+R and type in `notepad "%appdata%\Spore ModAPI Launcher\support.info"`. A notepad window will open, containing the location of the Launcher Kit files. + +*Note: For older Launcher Kit versions, you may need to use `notepad "%appdata%\Spore ModAPI Launcher\path.info"` instead.* Once located, you can simply delete the `Spore ModAPI Launcher Kit` folder. diff --git a/docs/_info/update-lk.md b/docs/_info/update-lk.md index c9ab982..9487b47 100644 --- a/docs/_info/update-lk.md +++ b/docs/_info/update-lk.md @@ -1,8 +1,8 @@ --- title: Updating Launcher Kit -description: The Launcher Kit will automatically check for updates every time you open the Launcher, Easy Installer, or Easy Uninstaller. If an update is available, you will be prompted to install it. +description: The Launcher Kit will automatically check for updates hourly when you open the Launcher, Easy Installer, or Easy Uninstaller. If an update is available, you will be prompted to install it. --- -The Launcher Kit will automatically check for updates every time you open the Launcher, Easy Installer, or Easy Uninstaller. If an update is available, you will be prompted to install it. +The Launcher Kit will automatically check for updates every hour when you open the Launcher, Easy Installer, or Easy Uninstaller. If an update is available, you will be prompted to install it. The latest Launcher Kit version is **{{ site.github.latest_release.name }}**. You can check your current version by opening the Easy Uninstaller and looking in the bottom left corner. @@ -16,9 +16,9 @@ If you need to manually update, for example on a computer with no internet conne --- ## About Updates -The Launcher Kit and the ModAPI Core DLLs are updated separately. The Launcher Kit is used to install, manage, and load mods, while the ModAPI Core DLLs are loaded into the game itself to enable additional capabilities for mods. Therefore, you may be prompted to update both. +The Launcher Kit and the ModAPI DLLs are updated separately. The Launcher Kit is used to install, manage, and load mods, while the ModAPI DLLs are loaded into the game itself to enable additional capabilities for mods. Therefore, you may be prompted to update both. -The Launcher Kit connects to the internet and attempts to update both. Updates for the Launcher Kit itself may be downloaded from the Spore Community Hub servers, or from GitHub. Updates for the ModAPI Core DLLs are downloaded from GitHub. +The Launcher Kit connects to the internet and attempts to update both. Updates for the Launcher Kit itself may be downloaded from the Spore Community Hub servers, or from GitHub. Updates for the ModAPI DLLs are downloaded from GitHub. No identifying information is sent to the servers to perform the update check. We do not track you or collect any telemetry data. @@ -27,8 +27,12 @@ No identifying information is sent to the servers to perform the update check. W ## Advanced topics The content below is intended for **advanced users**. These options are unsupported and you may not receive help if you use them. Use at your own risk. -### Changing update service -If you need to use a custom update service, place a file called `overrideUpdatePath.info` into `%appdata%\Spore ModAPI Launcher`. The file should contain the URL of the update service to use. This option is only intended for Launcher Kit developers who are running their own custom update service for development purposes. +### Forcing update check +The Launcher Kit stores the last update time in a file called `lastUpdateCheckTime.info` in `%appdata%\Spore ModAPI Launcher`. This is used to limit update checks to once per hour. If you need to update immediately, you may delete this file. + +When attempting to install a mod that requires a newer Launcher Kit or ModAPI DLLs version, this timer is reset automatically. ### Disabling update check -If you need to disable the update check, for example on a computer that is always offline and thus unable to connect to the internet, place a file called `noUpdateCheck.info` into `%appdata%\Spore ModAPI Launcher`. **Make sure you are checking the website and manually updating if needed.** You will not receive help if you are not using the latest Launcher Kit version. \ No newline at end of file +If you need to disable the update check, for example on a computer that is always offline and thus unable to connect to the internet, place a file called `noUpdateCheck.info` into `%appdata%\Spore ModAPI Launcher`. **Make sure you are checking the website and manually updating if needed.** You will not receive help if you are not using the latest Launcher Kit version. + +When updates are disabled, you will be prompted to re-enable them periodically, as well as when installing a mod that requires a newer Launcher Kit or ModAPI DLLs version. \ No newline at end of file diff --git a/docs/support.md b/docs/support.md index fbb380f..c9e9a49 100644 --- a/docs/support.md +++ b/docs/support.md @@ -16,10 +16,17 @@ description: Get help with Spore mods. Please read the suggested steps on this p If you have tried all of the above and still need help, please prepare the following information: - Where you got the game from (disc, EA App, Steam, or GOG) - Screenshot of the error message (if applicable) and/or a description of what is not working -- Screenshot(s) of the Easy Uninstaller, specifically showing both +- Screenshot(s) of the Easy Uninstaller, specifically showing all of the following: - Your current mod list + - Your current game version - Your current Launcher Kit version + - Your current ModAPI DLLs version Press Alt+PrtScr to copy a screenshot, then you can paste it directly into Discord or GitHub. -Submit the above information in the #mod-support channel of the [Spore Modding Community Discord]({{ page.smc_discord_url }}) (recommended), or, create an issue on [GitHub](https://github.com/Spore-Community/ModAPI-Launcher-Kit/issues/new/choose) and submit it there. We do not provide support via email or DMs. \ No newline at end of file +You may also be asked to send a support info file: +- Press Win+R and type in `notepad "%appdata%\Spore ModAPI Launcher\support.info"` +- A notepad window will open, copy and send the full contents of this file. +- If you get a prompt "Cannot find the file", make sure the Launcher Kit is up-to-date. + +Submit the above information in the #mod-support channel of the [Spore Modding Community Discord]({{ page.smc_discord_url }}) (recommended), or, create an issue on [GitHub](https://github.com/Spore-Community/ModAPI-Launcher-Kit/issues/new/choose) and submit it there. We do not provide technical support via email or DMs. \ No newline at end of file