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