1 /*
2 ** filesys_steam.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2013 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 ** notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 ** notice, this list of conditions and the following disclaimer in the
16 ** documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 ** derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34
35 #ifdef _WIN32
36 #define WIN32_LEAN_AND_MEAN
37 #define USE_WINDOWS_DWORD
38 #define USE_WINDOWS_BOOLEAN
39 #include <windows.h>
40 #undef ERROR
41 #else
42 #include "sys/stat.h"
43 #endif
44
45 #include "doomerrors.h"
46 #include "filesys.h"
47 #include "scanner.h"
48
49 namespace FileSys {
50
51 #ifdef _WIN32
52 /*
53 ** Bits and pieces
54 **
55 **---------------------------------------------------------------------------
56 ** Copyright 1998-2009 Randy Heit
57 ** All rights reserved.
58 **
59 ** License conditions same as BSD above.
60 **---------------------------------------------------------------------------
61 **
62 */
63 //==========================================================================
64 //
65 // QueryPathKey
66 //
67 // Returns the value of a registry key into the output variable value.
68 //
69 //==========================================================================
70
QueryPathKey(HKEY key,const char * keypath,const char * valname,FString & value)71 static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FString &value)
72 {
73 HKEY steamkey;
74 DWORD pathtype;
75 DWORD pathlen;
76 LONG res;
77
78 if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &steamkey))
79 {
80 if (ERROR_SUCCESS == RegQueryValueEx(steamkey, valname, 0, &pathtype, NULL, &pathlen) &&
81 pathtype == REG_SZ && pathlen != 0)
82 {
83 // Don't include terminating null in count
84 char *chars = value.LockNewBuffer(pathlen - 1);
85 res = RegQueryValueEx(steamkey, valname, 0, NULL, (LPBYTE)chars, &pathlen);
86 value.UnlockBuffer();
87 if (res != ERROR_SUCCESS)
88 {
89 value = "";
90 }
91 }
92 RegCloseKey(steamkey);
93 }
94 return value.IsNotEmpty();
95 }
96 #else
97 static TMap<int, FString> SteamAppInstallPath;
98 static void PSR_FindEndBlock(Scanner &sc)
99 {
100 int depth = 1;
101 do
102 {
103 if(sc.CheckToken('}'))
104 --depth;
105 else if(sc.CheckToken('{'))
106 ++depth;
107 else
108 sc.GetNextToken();
109 }
110 while(depth);
111 }
112 static void PSR_SkipBlock(Scanner &sc)
113 {
114 sc.MustGetToken('{');
115 PSR_FindEndBlock(sc);
116 }
117 static bool PSR_FindAndEnterBlock(Scanner &sc, const char* keyword)
118 {
119 // Finds a block with a given keyword and then enter it (opening brace)
120 // Should be closed with PSR_FindEndBlock
121 while(sc.TokensLeft())
122 {
123 if(sc.CheckToken('}'))
124 {
125 sc.Rewind();
126 return false;
127 }
128
129 sc.MustGetToken(TK_StringConst);
130 if(sc->str.CompareNoCase(keyword) != 0)
131 {
132 if(!sc.CheckToken(TK_StringConst))
133 PSR_SkipBlock(sc);
134 }
135 else
136 {
137 sc.MustGetToken('{');
138 return true;
139 }
140 }
141 return false;
142 }
143
144 static TArray<FString> PSR_ReadBaseInstalls(Scanner &sc)
145 {
146 TArray<FString> result;
147
148 // Get a list of possible install directories.
149 while(sc.TokensLeft())
150 {
151 if(sc.CheckToken('}'))
152 break;
153
154 sc.MustGetToken(TK_StringConst);
155 FString key(sc->str);
156 if(key.Left(18).CompareNoCase("BaseInstallFolder_") == 0)
157 {
158 sc.MustGetToken(TK_StringConst);
159 result.Push(sc->str + "/steamapps/common");
160 }
161 else
162 {
163 if(sc.CheckToken('{'))
164 PSR_FindEndBlock(sc);
165 else
166 sc.MustGetToken(TK_StringConst);
167 }
168 }
169
170 return result;
171 }
172 static TArray<FString> ParseSteamRegistry(const char* path)
173 {
174 TArray<FString> dirs;
175
176 char* data;
177 long size;
178
179 // Read registry data
180 FILE* registry = fopen(path, "rb");
181 if(!registry)
182 return dirs;
183
184 fseek(registry, 0, SEEK_END);
185 size = ftell(registry);
186 fseek(registry, 0, SEEK_SET);
187 data = new char[size];
188 fread(data, 1, size, registry);
189 fclose(registry);
190
191 Scanner sc(data, size);
192 delete[] data;
193
194 // Find the SteamApps listing
195 if(PSR_FindAndEnterBlock(sc, "InstallConfigStore"))
196 {
197 if(PSR_FindAndEnterBlock(sc, "Software"))
198 {
199 if(PSR_FindAndEnterBlock(sc, "Valve"))
200 {
201 if(PSR_FindAndEnterBlock(sc, "Steam"))
202 {
203 dirs = PSR_ReadBaseInstalls(sc);
204 }
205 PSR_FindEndBlock(sc);
206 }
207 PSR_FindEndBlock(sc);
208 }
209 PSR_FindEndBlock(sc);
210 }
211
212 return dirs;
213 }
214 #endif
215
GetSteamPath(ESteamApp game)216 FString GetSteamPath(ESteamApp game)
217 {
218 static struct SteamAppInfo
219 {
220 const char* const BasePath;
221 const int AppID;
222 } AppInfo[NUM_STEAM_APPS] =
223 {
224 {"Wolfenstein 3D", 2270},
225 {"Spear of Destiny", 9000},
226 {"The Apogee Throwback Pack", 238050},
227 {"Super 3-D Noah's Ark", 371180}
228 };
229
230 #if defined(_WIN32)
231 FString path;
232
233 //==========================================================================
234 //
235 // I_GetSteamPath
236 //
237 // Check the registry for the path to Steam, so that we can search for
238 // IWADs that were bought with Steam.
239 //
240 //==========================================================================
241
242 if (!QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path))
243 {
244 if(!QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path))
245 path = "";
246 }
247 if(!path.IsEmpty())
248 path += "\\SteamApps\\common";
249
250 if(path.IsEmpty())
251 return path;
252
253 return path + PATH_SEPARATOR + AppInfo[game].BasePath;
254 #else
255 // Linux and OS X actually allow the user to install to any location, so
256 // we need to figure out on an app-by-app basis where the game is installed.
257 // To do so, we read the virtual registry.
258 if(SteamAppInstallPath.CountUsed() == 0)
259 {
260 TArray<FString> SteamInstallFolders;
261
262 #ifdef __APPLE__
263 FString appSupportPath = OSX_FindFolder(DIR_ApplicationSupport);
264 FString regPath = appSupportPath + "/Steam/config/config.vdf";
265 try
266 {
267
268 SteamInstallFolders = ParseSteamRegistry(regPath);
269 }
270 catch(class CDoomError &error)
271 {
272 // If we can't parse for some reason just pretend we can't find anything.
273 return FString();
274 }
275
276 SteamInstallFolders.Push(appSupportPath + "/Steam/SteamApps/common");
277 #else
278 char* home = getenv("HOME");
279 if(home != NULL && *home != '\0')
280 {
281 FString regPath;
282 regPath.Format("%s/.local/share/Steam/config/config.vdf", home);
283 try
284 {
285 SteamInstallFolders = ParseSteamRegistry(regPath);
286 }
287 catch(class CDoomError &error)
288 {
289 // If we can't parse for some reason just pretend we can't find anything.
290 return FString();
291 }
292
293 regPath.Format("%s/.local/share/Steam/SteamApps/common", home);
294 SteamInstallFolders.Push(regPath);
295 }
296 #endif
297
298 for(unsigned int i = 0;i < SteamInstallFolders.Size();++i)
299 {
300 for(unsigned int app = 0;app < countof(AppInfo);++app)
301 {
302 struct stat st;
303 FString candidate(SteamInstallFolders[i] + "/" + AppInfo[app].BasePath);
304 if(stat(candidate, &st) == 0 && S_ISDIR(st.st_mode))
305 SteamAppInstallPath[AppInfo[app].AppID] = candidate;
306 }
307 }
308 }
309 const FString *installPath = SteamAppInstallPath.CheckKey(AppInfo[game].AppID);
310 if(installPath)
311 return *installPath;
312 return FString();
313 #endif
314 }
315
GetGOGPath(ESteamApp game)316 FString GetGOGPath(ESteamApp game)
317 {
318 static struct SteamAppInfo
319 {
320 const char* const AppID;
321 } AppInfo[NUM_STEAM_APPS] =
322 {
323 {"1441705046"}, // Wolfenstein 3D
324 {"1441705126"}, // Spear of Destiny
325 {NULL}, // Throwback Pack
326 {NULL} // Super 3D Noah's Ark
327 };
328
329 if(AppInfo[game].AppID == NULL)
330 return FString();
331
332 #if defined(_WIN32)
333 FString path;
334
335
336 //==========================================================================
337 //
338 // I_GetGogPaths
339 //
340 // Check the registry for GOG installation paths, so we can search for IWADs
341 // that were bought from GOG.com. This is a bit different from the Steam
342 // version because each game has its own independent installation path, no
343 // such thing as <steamdir>/SteamApps/common/<GameName>.
344 //
345 //==========================================================================
346
347 #ifdef _WIN64
348 FString gogregistrypath = "Software\\Wow6432Node\\GOG.com\\Games";
349 #else
350 // If a 32-bit ZDoom runs on a 64-bit Windows, this will be transparently and
351 // automatically redirected to the Wow6432Node address instead, so this address
352 // should be safe to use in all cases.
353 FString gogregistrypath = "Software\\GOG.com\\Games";
354 #endif
355
356 if(QueryPathKey(HKEY_LOCAL_MACHINE, gogregistrypath + PATH_SEPARATOR + AppInfo[game].AppID, "Path", path))
357 return path;
358 return FString();
359 #else
360 return FString();
361 #endif
362 }
363
364 }
365