Skip to content

Commit 344246a

Browse files
committed
Added game path and mods path autodetection feature to settings.
1 parent 9ba47e4 commit 344246a

4 files changed

Lines changed: 374 additions & 1 deletion

File tree

Pages/GamePathDetector.cs

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
using System;
2+
using System.Runtime.Versioning;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using Microsoft.Win32;
7+
8+
namespace SimTools
9+
{
10+
/// <summary>
11+
/// Probes common install locations and user-data folders to auto-populate
12+
/// game and mod directories in Settings. Detection is opportunistic — the
13+
/// first matching path wins for each game. Fields already set by the user
14+
/// are never overwritten by the caller.
15+
/// </summary>
16+
internal static class GamePathDetector
17+
{
18+
// ── Result type ──────────────────────────────────────────────────────────
19+
public sealed record DetectionResult(string? GamePath, string? ModPath);
20+
21+
// ── Well-known root paths ────────────────────────────────────────────────
22+
private static readonly string PF86 =
23+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
24+
private static readonly string PF =
25+
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
26+
private static readonly string Docs =
27+
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
28+
29+
// ── Steam ────────────────────────────────────────────────────────────────
30+
private static string? _steamCommon;
31+
32+
[SupportedOSPlatform("windows")]
33+
private static string? SteamCommon()
34+
{
35+
if (_steamCommon is not null) return _steamCommon;
36+
try
37+
{
38+
// Primary: registry
39+
var steamRoot =
40+
Registry.GetValue(@"HKEY_CURRENT_USER\Software\Valve\Steam",
41+
"SteamPath", null)?.ToString()?.Replace('/', '\\');
42+
43+
// Fallback: common install locations
44+
if (string.IsNullOrEmpty(steamRoot) || !Directory.Exists(steamRoot))
45+
{
46+
steamRoot = FindFirst(new[]
47+
{
48+
Path.Combine(PF86, "Steam"),
49+
Path.Combine(PF, "Steam"),
50+
@"C:\Steam",
51+
});
52+
}
53+
54+
if (steamRoot is not null)
55+
{
56+
var common = Path.Combine(steamRoot, "steamapps", "common");
57+
if (Directory.Exists(common))
58+
_steamCommon = common;
59+
}
60+
}
61+
catch { /* registry access denied — skip */ }
62+
return _steamCommon;
63+
}
64+
65+
// ── GOG ──────────────────────────────────────────────────────────────────
66+
private static string? _gogRoot;
67+
68+
private static string? GogRoot()
69+
{
70+
if (_gogRoot is not null) return _gogRoot;
71+
try
72+
{
73+
// Try common GOG Games directories
74+
_gogRoot = FindFirst(new[]
75+
{
76+
@"C:\GOG Games",
77+
Path.Combine(PF86, "GOG Games"),
78+
Path.Combine(PF, "GOG Games"),
79+
});
80+
}
81+
catch { }
82+
return _gogRoot;
83+
}
84+
85+
// ── EA / Origin root directories ─────────────────────────────────────────
86+
// Yields every vendor subfolder that actually exists on this machine.
87+
private static IEnumerable<string> EaRoots()
88+
{
89+
foreach (var pf in new[] { PF86, PF })
90+
foreach (var vendor in new[] { "EA Games", "Electronic Arts", "Origin Games" })
91+
{
92+
var p = Path.Combine(pf, vendor);
93+
if (Directory.Exists(p)) yield return p;
94+
}
95+
}
96+
97+
// ── Maxis root directories ────────────────────────────────────────────────
98+
private static IEnumerable<string> MaxisRoots()
99+
{
100+
foreach (var pf in new[] { PF86, PF })
101+
{
102+
var p = Path.Combine(pf, "Maxis");
103+
if (Directory.Exists(p)) yield return p;
104+
}
105+
}
106+
107+
// ── Helper: first existing path ───────────────────────────────────────────
108+
private static string? FindFirst(IEnumerable<string> candidates)
109+
=> candidates.FirstOrDefault(Directory.Exists);
110+
111+
// ── Game detectors ────────────────────────────────────────────────────────
112+
113+
private static DetectionResult DetectSims1() => new(
114+
GamePath: FindFirst(
115+
MaxisRoots().Select(r => Path.Combine(r, "The Sims"))
116+
.Concat(EaRoots().Select(r => Path.Combine(r, "The Sims")))),
117+
ModPath: null);
118+
119+
private static DetectionResult DetectSims2()
120+
{
121+
var game = FindFirst(
122+
EaRoots().SelectMany(r => new[]
123+
{
124+
Path.Combine(r, "The Sims 2"),
125+
Path.Combine(r, "The Sims 2 Legacy Collection"),
126+
Path.Combine(r, "The Sims 2 Ultimate Collection"),
127+
}));
128+
129+
var mods = FindFirst(new[]
130+
{
131+
Path.Combine(Docs, "EA Games", "The Sims 2", "Downloads"),
132+
Path.Combine(Docs, "EA Games", "The Sims 2 Legacy Collection", "Downloads"),
133+
Path.Combine(Docs, "EA Games", "The Sims 2 Ultimate Collection", "Downloads"),
134+
});
135+
136+
return new DetectionResult(game, mods);
137+
}
138+
139+
private static DetectionResult DetectSimsLifeStories() => new(
140+
GamePath: FindFirst(EaRoots().Select(r => Path.Combine(r, "The Sims Life Stories"))),
141+
ModPath: FindFirst(new[]
142+
{
143+
Path.Combine(Docs, "EA Games", "The Sims Life Stories", "Collections"),
144+
}));
145+
146+
private static DetectionResult DetectSimsPetStories() => new(
147+
GamePath: FindFirst(EaRoots().Select(r => Path.Combine(r, "The Sims Pet Stories"))),
148+
ModPath: FindFirst(new[]
149+
{
150+
Path.Combine(Docs, "EA Games", "The Sims Pet Stories", "Collections"),
151+
}));
152+
153+
private static DetectionResult DetectSimsCastawayStories() => new(
154+
GamePath: FindFirst(EaRoots().Select(r => Path.Combine(r, "The Sims Castaway Stories"))),
155+
ModPath: FindFirst(new[]
156+
{
157+
Path.Combine(Docs, "EA Games", "The Sims Castaway Stories", "Collections"),
158+
}));
159+
160+
private static DetectionResult DetectSims3()
161+
{
162+
var candidates = EaRoots().Select(r => Path.Combine(r, "The Sims 3"));
163+
var sc = SteamCommon();
164+
if (sc is not null)
165+
candidates = candidates.Append(Path.Combine(sc, "The Sims 3"));
166+
167+
return new DetectionResult(
168+
GamePath: FindFirst(candidates),
169+
ModPath: FindFirst(new[]
170+
{
171+
Path.Combine(Docs, "Electronic Arts", "The Sims 3", "Mods"),
172+
}));
173+
}
174+
175+
private static DetectionResult DetectSims4()
176+
{
177+
var candidates = EaRoots().Select(r => Path.Combine(r, "The Sims 4"));
178+
var sc = SteamCommon();
179+
if (sc is not null)
180+
candidates = candidates.Append(Path.Combine(sc, "The Sims 4"));
181+
182+
return new DetectionResult(
183+
GamePath: FindFirst(candidates),
184+
ModPath: FindFirst(new[]
185+
{
186+
Path.Combine(Docs, "Electronic Arts", "The Sims 4", "Mods"),
187+
}));
188+
}
189+
190+
private static DetectionResult DetectSimsMedieval()
191+
{
192+
var candidates = EaRoots().Select(r => Path.Combine(r, "The Sims Medieval"));
193+
var sc = SteamCommon();
194+
if (sc is not null)
195+
candidates = candidates.Append(Path.Combine(sc, "The Sims Medieval"));
196+
197+
return new DetectionResult(
198+
GamePath: FindFirst(candidates),
199+
ModPath: FindFirst(new[]
200+
{
201+
Path.Combine(Docs, "Electronic Arts", "The Sims Medieval", "Mods"),
202+
}));
203+
}
204+
205+
private static DetectionResult DetectSimCopter() => new(
206+
GamePath: FindFirst(
207+
MaxisRoots().Select(r => Path.Combine(r, "SimCopter"))
208+
.Concat(EaRoots().Select(r => Path.Combine(r, "SimCopter")))),
209+
ModPath: null);
210+
211+
private static DetectionResult DetectStreetsOfSimCity() => new(
212+
GamePath: FindFirst(
213+
MaxisRoots().Select(r => Path.Combine(r, "Streets of SimCity"))
214+
.Concat(EaRoots().Select(r => Path.Combine(r, "Streets of SimCity")))),
215+
ModPath: null);
216+
217+
private static DetectionResult DetectSimCity2000()
218+
{
219+
var candidates =
220+
MaxisRoots().SelectMany(r => new[]
221+
{
222+
Path.Combine(r, "Sim City 2000"),
223+
Path.Combine(r, "SimCity 2000"),
224+
});
225+
226+
var gog = GogRoot();
227+
if (gog is not null)
228+
candidates = candidates.Append(Path.Combine(gog, "SimCity 2000 Special Edition"));
229+
230+
return new DetectionResult(FindFirst(candidates), null);
231+
}
232+
233+
private static DetectionResult DetectSimCity3000() => new(
234+
GamePath: FindFirst(
235+
MaxisRoots().SelectMany(r => new[]
236+
{
237+
Path.Combine(r, "SimCity 3000 Unlimited"),
238+
Path.Combine(r, "SimCity 3000"),
239+
})),
240+
ModPath: null);
241+
242+
private static DetectionResult DetectSimCity4()
243+
{
244+
var candidates =
245+
MaxisRoots().SelectMany(r => new[]
246+
{
247+
Path.Combine(r, "SimCity 4 Deluxe"),
248+
Path.Combine(r, "SimCity 4"),
249+
})
250+
.Concat(EaRoots().Select(r => Path.Combine(r, "SimCity 4 Deluxe")));
251+
252+
var sc = SteamCommon();
253+
if (sc is not null)
254+
candidates = candidates.Append(Path.Combine(sc, "SimCity 4 Deluxe Edition"));
255+
256+
var gog = GogRoot();
257+
if (gog is not null)
258+
candidates = candidates.Append(Path.Combine(gog, "SimCity 4 Deluxe Edition"));
259+
260+
return new DetectionResult(FindFirst(candidates), null);
261+
}
262+
263+
private static DetectionResult DetectSimCity2013()
264+
{
265+
var candidates = EaRoots().Select(r => Path.Combine(r, "SimCity"))
266+
.Concat(new[]
267+
{
268+
Path.Combine(PF86, "Origin Games", "SimCity"),
269+
Path.Combine(PF, "Origin Games", "SimCity"),
270+
});
271+
272+
return new DetectionResult(FindFirst(candidates), null);
273+
}
274+
275+
// ── Public entry point ───────────────────────────────────────────────────
276+
/// <summary>
277+
/// Probes all supported games and returns the best match found for each.
278+
/// Null values mean the path could not be determined automatically.
279+
/// </summary>
280+
[SupportedOSPlatform("windows")]
281+
public static Dictionary<string, DetectionResult> DetectAll() => new()
282+
{
283+
["Sims1"] = DetectSims1(),
284+
["Sims2"] = DetectSims2(),
285+
["SimsLifeStories"] = DetectSimsLifeStories(),
286+
["SimsPetStories"] = DetectSimsPetStories(),
287+
["SimsCastawayStories"] = DetectSimsCastawayStories(),
288+
["Sims3"] = DetectSims3(),
289+
["Sims4"] = DetectSims4(),
290+
["SimsMedieval"] = DetectSimsMedieval(),
291+
["SimCopter"] = DetectSimCopter(),
292+
["StreetsOfSimCity"] = DetectStreetsOfSimCity(),
293+
["SimCity2000"] = DetectSimCity2000(),
294+
["SimCity3000"] = DetectSimCity3000(),
295+
["SimCity4"] = DetectSimCity4(),
296+
["SimCity2013"] = DetectSimCity2013(),
297+
};
298+
}
299+
}

Pages/SettingsWindow.xaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,27 @@
185185
Style="{StaticResource SectionHeaderText}"
186186
Text="Game &amp; Mod Directories"/>
187187

188+
<!-- ── Auto-Detect row ──────────────────────────────────── -->
189+
<Grid Margin="0 6 0 12">
190+
<Grid.ColumnDefinitions>
191+
<ColumnDefinition Width="*"/>
192+
<ColumnDefinition Width="Auto"/>
193+
</Grid.ColumnDefinitions>
194+
<TextBlock Grid.Column="0"
195+
Style="{StaticResource RowLabelText}"
196+
TextWrapping="Wrap"
197+
VerticalAlignment="Top"
198+
Text="SimTools will attempt to locate your game and mod directories automatically. Only empty fields are filled — existing paths are never overwritten." HorizontalAlignment="Left" Width="450"/>
199+
<Button x:Name="AutoDetectBtn"
200+
Grid.Column="1"
201+
Style="{StaticResource DarkButton}"
202+
Content="Auto-Detect All"
203+
MinWidth="120"
204+
Margin="12 0 0 0"
205+
Background="White" Foreground="#FF373737"
206+
Click="AutoDetect_Click"/>
207+
</Grid>
208+
188209
<ItemsControl x:Name="GameDirsControl">
189210
<ItemsControl.ItemTemplate>
190211
<DataTemplate>

0 commit comments

Comments
 (0)