-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathSporePath.cs
More file actions
240 lines (209 loc) · 9.93 KB
/
SporePath.cs
File metadata and controls
240 lines (209 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
using Microsoft.Win32;
using System.IO;
namespace ModAPI.Common
{
public static class SporePath
{
public enum Game
{
None,
GalacticAdventures,
Spore,
CreepyAndCute
}
// Some things for Steam
public static readonly string SteamAppsKey = @"HKEY_CURRENT_USER\Software\Valve\Steam\Apps\";
public static readonly string GalacticAdventuresSteamID = "24720";
private static string _sporebinEP1Path = null;
/// <summary>
/// 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.
/// </summary>
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)
{
return null;
}
var gameRootPath = Directory.GetParent(dataPath).FullName;
var binPath = Path.Combine(gameRootPath, "SporebinEP1");
// Verify that this folder actually contains SporeApp.exe
if (!File.Exists(Path.Combine(binPath, "SporeApp.exe")))
{
return null;
}
return _sporebinEP1Path = binPath;
}
/// <summary>
/// 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.
/// </summary>
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;
}
// Remove "" if necessary
path = FixPath(path);
// Fix slashes if necessary (GOG .22 has mixed slashes)
path = Path.GetFullPath(path);
// Remove trailing slash if present
path = path.TrimEnd(Path.DirectorySeparatorChar);
// Verify that folder actually exists
if (!Directory.Exists(path))
{
return null;
}
return path;
}
/// <summary>
/// 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.
/// </summary>
public static bool IsInstalled(Game game)
{
return GetDataPath(game) != null;
}
// remove "" if necessary
private static string FixPath(string path)
{
if (path.StartsWith("\""))
{
return path.Substring(1, path.Length - 2);
}
else
{
return path;
}
}
public static bool SporeIsInstalledOnSteam()
{
object result = Registry.GetValue(SteamAppsKey + GalacticAdventuresSteamID, "Installed", 0);
// returns null if the key does not exist, or default value if the key existed but the value did not
return result != null && ((int)result != 0);
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public static bool IsGameInstalled(bool showMessageWhenFalse)
{
// Make sure data dirs are present for Spore and GA
if (!IsInstalled(Game.Spore) || !IsInstalled(Game.GalacticAdventures))
{
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;
}
// 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())
{
if (showMessageWhenFalse)
{
SupportInfo.ShowWarning(CommonStrings.SteamDownloadedButNotLaunched, CommonStrings.SteamDownloadedButNotLaunchedTitle);
}
return false;
}
return true;
}
/// <summary>
/// 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.
/// </summary>
private static object GetFromRegistry(Game game, string valueName)
{
foreach (string key in GetRegistryKeySuffixes(game))
{
var value = GetFromRegistry(key, valueName);
if (value != null)
{
return value;
}
}
return null;
}
/// <summary>
/// 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.
/// </summary>
private static object GetFromRegistry(string keyName, string valueName)
{
// 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\\";
var value = Registry.GetValue(keyPrefix64 + keyName, valueName, null);
return value ?? Registry.GetValue(keyPrefix32 + keyName, valueName, null);
}
/// <summary>
/// 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.
/// </summary>
private static string[] GetRegistryKeySuffixes(Game game)
{
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[] { };
}
}
}
}