1 /*
2  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3  * It is copyright by its individual contributors, as recorded in the
4  * project's Git history.  See COPYING.txt at the top level for license
5  * terms and a link to the Git history.
6  */
7 /*
8  *
9  * Some simple physfs extensions
10  *
11  */
12 
13 #include <cstdlib>
14 #if !defined(macintosh) && !defined(_MSC_VER)
15 #include <sys/param.h>
16 #endif
17 #if defined(__APPLE__) && defined(__MACH__)
18 #include <sys/mount.h>
19 #include <unistd.h>	// for chdir hack
20 #include <ApplicationServices/ApplicationServices.h>
21 #endif
22 
23 #include "args.h"
24 #include "newdemo.h"
25 #include "console.h"
26 #include "strutil.h"
27 #include "ignorecase.h"
28 #include "physfs_list.h"
29 
30 #include "compiler-range_for.h"
31 #include "compiler-poison.h"
32 #include "partial_range.h"
33 
34 namespace dcx {
35 
36 const std::array<file_extension_t, 1> archive_exts{{"dxa"}};
37 
get(char * const buf,std::size_t n,PHYSFS_File * const fp)38 char *PHYSFSX_fgets_t::get(char *const buf, std::size_t n, PHYSFS_File *const fp)
39 {
40 	PHYSFS_sint64 r = PHYSFS_read(fp, buf, sizeof(*buf), n - 1);
41 	if (r <= 0)
42 		return DXX_POISON_MEMORY(buf, buf + n, 0xcc), nullptr;
43 	char *p = buf;
44 	const auto cleanup = [&]{
45 		return *p = 0, DXX_POISON_MEMORY(p + 1, buf + n, 0xcc), p;
46 	};
47 	char *const e = &buf[r];
48 	for (;;)
49 	{
50 		if (p == e)
51 		{
52 			return cleanup();
53 		}
54 		char c = *p;
55 		if (c == 0)
56 			break;
57 		if (c == '\n')
58 		{
59 			break;
60 		}
61 		else if (c == '\r')
62 		{
63 			*p = 0;
64 			if (++p != e && *p != '\n')
65 				--p;
66 			break;
67 		}
68 		++p;
69 	}
70 	PHYSFS_seek(fp, PHYSFS_tell(fp) + p - e + 1);
71 	return cleanup();
72 }
73 
PHYSFSX_checkMatchingExtension(const char * filename,const partial_range_t<const file_extension_t * > range)74 int PHYSFSX_checkMatchingExtension(const char *filename, const partial_range_t<const file_extension_t *> range)
75 {
76 	const char *ext = strrchr(filename, '.');
77 	if (!ext)
78 		return 0;
79 	++ext;
80 	// see if the file is of a type we want
81 	range_for (auto &k, range)
82 	{
83 		if (!d_stricmp(ext, k))
84 			return 1;
85 	}
86 	return 0;
87 }
88 
89 }
90 
91 namespace dsx {
92 
93 // Initialise PhysicsFS, set up basic search paths and add arguments from .ini file.
94 // The .ini file can be in either the user directory or the same directory as the program.
95 // The user directory is searched first.
PHYSFSX_init(int argc,char * argv[])96 bool PHYSFSX_init(int argc, char *argv[])
97 {
98 #if defined(__unix__) || defined(__APPLE__) || defined(__MACH__)
99 	char fullPath[PATH_MAX + 5];
100 #endif
101 #ifdef macintosh	// Mac OS 9
102 	char base_dir[PATH_MAX];
103 	int bundle = 0;
104 #else
105 #define base_dir PHYSFS_getBaseDir()
106 #endif
107 
108 	if (!PHYSFS_init(argv[0]))
109 		Error("Failed to init PhysFS: %s", PHYSFS_getLastError());
110 	PHYSFS_permitSymbolicLinks(1);
111 
112 #ifdef macintosh
113 	strcpy(base_dir, PHYSFS_getBaseDir());
114 	if (strstr(base_dir, ".app:Contents:MacOSClassic"))	// the Mac OS 9 program is still in the .app bundle
115 	{
116 		char *p;
117 
118 		bundle = 1;
119 		if (base_dir[strlen(base_dir) - 1] == ':')
120 			base_dir[strlen(base_dir) - 1] = '\0';
121 		p = strrchr(base_dir, ':'); *p = '\0';	// path to 'Contents'
122 		p = strrchr(base_dir, ':'); *p = '\0';	// path to bundle
123 		p = strrchr(base_dir, ':'); *p = '\0';	// path to directory containing bundle
124 	}
125 #endif
126 
127 #if (defined(__APPLE__) && defined(__MACH__))	// others?
128 	chdir(base_dir);	// make sure relative hogdir paths work
129 #endif
130 
131 	const char *writedir;
132 #if defined(__unix__)
133 #if defined(DXX_BUILD_DESCENT_I)
134 #define DESCENT_PATH_NUMBER	"1"
135 #elif defined(DXX_BUILD_DESCENT_II)
136 #define DESCENT_PATH_NUMBER	"2"
137 #endif
138 	const auto &home_environ_var = "D" DESCENT_PATH_NUMBER "X_REBIRTH_HOME";
139 	const char *path = getenv(home_environ_var);
140 	if (!path)
141 	{
142 		path = getenv(&home_environ_var[4]);
143 		if (!path)
144 # if !(defined(__APPLE__) && defined(__MACH__))
145 	path = "~/.d" DESCENT_PATH_NUMBER "x-rebirth/";
146 # else
147 	path = "~/Library/Preferences/D" DESCENT_PATH_NUMBER "X Rebirth/";
148 # endif
149 	}
150 
151 	if (path[0] == '~') // yes, this tilde can be put before non-unix paths.
152 	{
153 		const char *home = PHYSFS_getUserDir();
154 		path++;
155 		// prepend home to the path
156 		if (*path == *PHYSFS_getDirSeparator())
157 			path++;
158 		snprintf(fullPath, sizeof(fullPath), "%s%s", home, path);
159 	}
160 	else
161 	{
162 		fullPath[sizeof(fullPath) - 1] = 0;
163 		strncpy(fullPath, path, sizeof(fullPath) - 1);
164 	}
165 
166 	PHYSFS_setWriteDir(fullPath);
167 	if (!(writedir = PHYSFS_getWriteDir()))
168 	{                                               // need to make it
169 		char *p;
170 		char ancestor[PATH_MAX + 5];    // the directory which actually exists
171 		char child[PATH_MAX + 5];               // the directory relative to the above we're trying to make
172 
173 		strcpy(ancestor, fullPath);
174 		const auto separator = *PHYSFS_getDirSeparator();
175 		while (!PHYSFS_getWriteDir() && (p = strrchr(ancestor, separator)))
176 		{
177 			if (p[1] == 0)
178 			{                                       // separator at the end (intended here, for safety)
179 				*p = 0;                 // kill this separator
180 				if (!(p = strrchr(ancestor, separator)))
181 					break;          // give up, this is (usually) the root directory
182 			}
183 
184 			p[1] = 0;                       // go to parent
185 			PHYSFS_setWriteDir(ancestor);
186 		}
187 
188 		strcpy(child, fullPath + strlen(ancestor));
189 		if (separator != '/')
190 			for (p = child; (p = strchr(p, separator)); p++)
191 				*p = '/';
192 		PHYSFS_mkdir(child);
193 		PHYSFS_setWriteDir(fullPath);
194 		writedir = PHYSFS_getWriteDir();
195 	}
196 	con_printf(CON_DEBUG, "PHYSFS: append write directory \"%s\" to search path", writedir);
197 	PHYSFS_mount(writedir, nullptr, 1);
198 #endif
199 	con_printf(CON_DEBUG, "PHYSFS: temporarily append base directory \"%s\" to search path", base_dir);
200 	PHYSFS_mount(base_dir, nullptr, 1);
201 	if (!InitArgs( argc,argv ))
202 		return false;
203 	PHYSFS_unmount(base_dir);
204 
205 	if (!PHYSFS_getWriteDir())
206 	{
207 		PHYSFS_setWriteDir(base_dir);
208 		if (!(writedir = PHYSFS_getWriteDir()))
209 			Error("can't set write dir: %s\n", PHYSFS_getLastError());
210 		PHYSFS_mount(writedir, nullptr, 0);
211 	}
212 
213 	//tell PHYSFS where hogdir is
214 	if (!CGameArg.SysHogDir.empty())
215 	{
216 		const auto p = CGameArg.SysHogDir.c_str();
217 		con_printf(CON_DEBUG, "PHYSFS: append argument hog directory \"%s\" to search path", p);
218 		PHYSFS_mount(p, nullptr, 1);
219 	}
220 #if DXX_USE_SHAREPATH
221 	else if (!GameArg.SysNoHogDir)
222 	{
223 		con_puts(CON_DEBUG, "PHYSFS: append sharepath directory \"" DXX_SHAREPATH "\" to search path");
224 		PHYSFS_mount(DXX_SHAREPATH, nullptr, 1);
225 	}
226 	else
227 	{
228 		con_puts(CON_DEBUG, "PHYSFS: skipping built-in sharepath \"" DXX_SHAREPATH "\"");
229 	}
230 #else
231 	else
232 	{
233 		con_puts(CON_DEBUG, "PHYSFS: no built-in sharepath");
234 	}
235 #endif
236 
237 	std::array<char, PATH_MAX> pathname;
238 	PHYSFSX_addRelToSearchPath("data", pathname, physfs_search_path::append);	// 'Data' subdirectory
239 
240 	// For Macintosh, add the 'Resources' directory in the .app bundle to the searchpaths
241 #if defined(__APPLE__) && defined(__MACH__)
242 	{
243 		CFBundleRef mainBundle = CFBundleGetMainBundle();
244 		if (mainBundle)
245 		{
246 			CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
247 			if (resourcesURL)
248 			{
249 				if (CFURLGetFileSystemRepresentation(resourcesURL, TRUE, reinterpret_cast<uint8_t *>(fullPath), sizeof(fullPath)))
250 				{
251 					con_printf(CON_DEBUG, "PHYSFS: append resources directory \"%s\" to search path", fullPath);
252 					PHYSFS_mount(fullPath, nullptr, 1);
253 				}
254 
255 				CFRelease(resourcesURL);
256 			}
257 		}
258 	}
259 #elif defined(macintosh)
260 	if (bundle)
261 	{
262 		base_dir[strlen(base_dir)] = ':';	// go back in the bundle
263 		base_dir[strlen(base_dir)] = ':';	// go back in 'Contents'
264 		strncat(base_dir, ":Resources:", PATH_MAX - 1 - strlen(base_dir));
265 		base_dir[PATH_MAX - 1] = '\0';
266 		con_printf(CON_DEBUG, "PHYSFS: append bundle directory \"%s\" to search path", base_dir);
267 		PHYSFS_mount(base_dir, nullptr, 1);
268 	}
269 #endif
270 	return true;
271 }
272 
273 #if defined(DXX_BUILD_DESCENT_II)
make_PHYSFSX_ComputedPathMount(const char * const name1,const char * const name2,physfs_search_path position)274 RAIIPHYSFS_ComputedPathMount make_PHYSFSX_ComputedPathMount(const char *const name1, const char *const name2, physfs_search_path position)
275 {
276 	auto pathname = std::make_unique<std::array<char, PATH_MAX>>();
277 	if (PHYSFSX_addRelToSearchPath(name1, *pathname.get(), position) == PHYSFS_ERR_OK ||
278 		PHYSFSX_addRelToSearchPath(name2, *pathname.get(), position) == PHYSFS_ERR_OK)
279 		return RAIIPHYSFS_ComputedPathMount(std::move(pathname));
280 	return nullptr;
281 }
282 #endif
283 
284 }
285 
286 namespace dcx {
287 
288 // Add a searchpath, but that searchpath is relative to an existing searchpath
289 // It will add the first one it finds and return 1, if it doesn't find any it returns 0
PHYSFSX_addRelToSearchPath(const char * relname,std::array<char,PATH_MAX> & pathname,physfs_search_path add_to_end)290 PHYSFS_ErrorCode PHYSFSX_addRelToSearchPath(const char *relname, std::array<char, PATH_MAX> &pathname, physfs_search_path add_to_end)
291 {
292 	char relname2[PATH_MAX];
293 
294 	snprintf(relname2, sizeof(relname2), "%s", relname);
295 	PHYSFSEXT_locateCorrectCase(relname2);
296 
297 	if (!PHYSFSX_getRealPath(relname2, pathname))
298 	{
299 		/* This failure is not reported as an error, because callers
300 		 * probe for files that users may not have, and do not need.
301 		 */
302 		con_printf(CON_DEBUG, "PHYSFS: ignoring map request: no canonical path for relative name \"%s\"", relname2);
303 		return PHYSFS_ERR_OK;
304 	}
305 
306 	auto r = PHYSFS_mount(pathname.data(), nullptr, static_cast<int>(add_to_end));
307 	const auto action = add_to_end != physfs_search_path::prepend ? "append" : "insert";
308 	if (r)
309 	{
310 		con_printf(CON_DEBUG, "PHYSFS: %s canonical directory \"%s\" to search path from relative name \"%s\"", action, pathname.data(), relname);
311 		return PHYSFS_ERR_OK;
312 	}
313 	else
314 	{
315 		const auto err = PHYSFS_getLastErrorCode();
316 		con_printf(CON_VERBOSE, "PHYSFS: failed to %s canonical directory \"%s\" to search path from relative name \"%s\": \"%s\"", action, pathname.data(), relname, PHYSFS_getErrorByCode(err));
317 		return err;
318 	}
319 }
320 
PHYSFSX_removeRelFromSearchPath(const char * relname)321 void PHYSFSX_removeRelFromSearchPath(const char *relname)
322 {
323 	char relname2[PATH_MAX];
324 
325 	snprintf(relname2, sizeof(relname2), "%s", relname);
326 	PHYSFSEXT_locateCorrectCase(relname2);
327 
328 	std::array<char, PATH_MAX> pathname;
329 	if (!PHYSFSX_getRealPath(relname2, pathname))
330 	{
331 		con_printf(CON_DEBUG, "PHYSFS: ignoring unmap request: no canonical path for relative name \"%s\"", relname2);
332 		return;
333 	}
334 	auto r = PHYSFS_unmount(pathname.data());
335 	if (r)
336 		con_printf(CON_DEBUG, "PHYSFS: unmap canonical directory \"%s\" (relative name \"%s\")", pathname.data(), relname);
337 	else
338 		con_printf(CON_VERBOSE, "PHYSFS: failed to unmap canonical directory \"%s\" (relative name \"%s\"): \"%s\"", pathname.data(), relname, PHYSFS_getLastError());
339 }
340 
PHYSFSX_fsize(const char * hogname)341 int PHYSFSX_fsize(const char *hogname)
342 {
343 	char hogname2[PATH_MAX];
344 
345 	snprintf(hogname2, sizeof(hogname2), "%s", hogname);
346 	PHYSFSEXT_locateCorrectCase(hogname2);
347 
348 	if (RAIIPHYSFS_File fp{PHYSFS_openRead(hogname2)})
349 		return PHYSFS_fileLength(fp);
350 	return -1;
351 }
352 
PHYSFSX_listSearchPathContent()353 void PHYSFSX_listSearchPathContent()
354 {
355 	con_puts(CON_DEBUG, "PHYSFS: Listing contents of Search Path.");
356 	PHYSFSX_uncounted_list list{PHYSFS_getSearchPath()};
357 	range_for (const auto i, list)
358 		con_printf(CON_DEBUG, "PHYSFS: [%s] is in the Search Path.", i);
359 	list.reset();
360 	list.reset(PHYSFS_enumerateFiles(""));
361 	range_for (const auto i, list)
362 		con_printf(CON_DEBUG, "PHYSFS: * We've got [%s].", i);
363 }
364 
365 }
366 
367 namespace dsx {
368 
369 // checks which archives are supported by PhysFS. Return 0 if some essential (HOG) is not supported
PHYSFSX_checkSupportedArchiveTypes()370 int PHYSFSX_checkSupportedArchiveTypes()
371 {
372 	int hog_sup = 0;
373 #ifdef DXX_BUILD_DESCENT_II
374 	int mvl_sup = 0;
375 #endif
376 
377 	con_puts(CON_DEBUG, "PHYSFS: Checking supported archive types.");
378 	range_for (const auto i, make_null_sentinel_array(PHYSFS_supportedArchiveTypes()))
379 	{
380 		const auto iextension = i->extension;
381 		con_printf(CON_DEBUG, "PHYSFS: Supported archive: [%s], which is [%s].", iextension, i->description);
382 		if (!d_stricmp(iextension, "HOG"))
383 			hog_sup = 1;
384 #ifdef DXX_BUILD_DESCENT_II
385 		else if (!d_stricmp(iextension, "MVL"))
386 			mvl_sup = 1;
387 #endif
388 	}
389 
390 	if (!hog_sup)
391 		con_puts(CON_CRITICAL, "PHYSFS: HOG not supported. The game will not work without!");
392 #ifdef DXX_BUILD_DESCENT_II
393 	if (!mvl_sup)
394 		con_puts(CON_URGENT, "PHYSFS: MVL not supported. Won't be able to play movies!");
395 #endif
396 
397 	return hog_sup;
398 }
399 
400 }
401 
402 namespace dcx {
403 
PHYSFSX_getRealPath(const char * stdPath,std::array<char,PATH_MAX> & realPath)404 int PHYSFSX_getRealPath(const char *stdPath, std::array<char, PATH_MAX> &realPath)
405 {
406 	DXX_POISON_MEMORY(realPath.data(), realPath.size(), 0xdd);
407 	const char *realDir = PHYSFS_getRealDir(stdPath);
408 	if (!realDir)
409 	{
410 		realDir = PHYSFS_getWriteDir();
411 		if (!realDir)
412 			return 0;
413 	}
414 	const auto realDirSize = strlen(realDir);
415 	if (realDirSize >= realPath.size())
416 		return 0;
417 	auto mountpoint = PHYSFS_getMountPoint(realDir);
418 	if (!mountpoint)
419 		return 0;
420 	std::copy_n(realDir, realDirSize + 1, realPath.begin());
421 #ifdef _unix__
422 	auto &sep = "/";
423 	assert(!strcmp(PHYSFS_getDirSeparator(), sep));
424 #else
425 	const auto sep = PHYSFS_getDirSeparator();
426 #endif
427 	const auto sepSize = strlen(sep);
428 	auto realPathUsed = realDirSize;
429 	if (realDirSize >= sepSize)
430 	{
431 		const auto p = std::next(realPath.begin(), realDirSize - sepSize);
432 		if (strcmp(p, sep)) // no sep at end of realPath
433 		{
434 			realPathUsed += sepSize;
435 			std::copy_n(sep, sepSize, &realPath[realDirSize]);
436 		}
437 	}
438 	if (*mountpoint == '/')
439 		++mountpoint;
440 	if (*stdPath == '/')
441 		++stdPath;
442 	const auto ml = strlen(mountpoint);
443 	if (!strncmp(mountpoint, stdPath, ml))
444 		stdPath += ml;
445 	else
446 	{
447 		/* Virtual path is not under the virtual mount point that
448 		 * provides the path.
449 		 */
450 		assert(false);
451 	}
452 	const auto stdPathLen = strlen(stdPath) + 1;
453 	if (realPathUsed + stdPathLen >= realPath.size())
454 		return 0;
455 #ifdef __unix__
456 	/* Separator is "/" and physfs internal separator is "/".  Copy
457 	 * through.
458 	 */
459 	std::copy_n(stdPath, stdPathLen, &realPath[realPathUsed]);
460 #else
461 	/* Separator might be / on non-unix, but the fallback path works
462 	 * regardless of whether separator is "/".
463 	 */
464 	const auto csep = *sep;
465 	const auto a = [csep](char c) {
466 		return c == '/' ? csep : c;
467 	};
468 	std::transform(stdPath, &stdPath[stdPathLen], &realPath[realPathUsed], a);
469 #endif
470 	return 1;
471 }
472 
PHYSFSX_rename(const char * oldpath,const char * newpath)473 int PHYSFSX_rename(const char *oldpath, const char *newpath)
474 {
475 	std::array<char, PATH_MAX> old, n;
476 	PHYSFSX_getRealPath(oldpath, old);
477 	PHYSFSX_getRealPath(newpath, n);
478 	return (rename(old.data(), n.data()) == 0);
479 }
480 
481 template <typename F>
PHYSFSX_findPredicateFiles(const char * path,F f)482 static inline PHYSFSX_uncounted_list PHYSFSX_findPredicateFiles(const char *path, F f)
483 {
484 	PHYSFSX_uncounted_list list{PHYSFS_enumerateFiles(path)};
485 	if (!list)
486 		return nullptr;	// out of memory: not so good
487 	char **j = list.get();
488 	range_for (const auto i, list)
489 	{
490 		if (f(i))
491 			*j++ = i;
492 		else
493 			free(i);
494 	}
495 	*j = NULL;
496 	char **r = reinterpret_cast<char **>(realloc(list.get(), (j - list.get() + 1)*sizeof(char *)));	// save a bit of memory (or a lot?)
497 	if (r)
498 	{
499 		list.release();
500 		list.reset(r);
501 	}
502 	return list;
503 }
504 
505 // Find files at path that have an extension listed in exts
506 // The extension list exts must be NULL-terminated, with each ext beginning with a '.'
PHYSFSX_findFiles(const char * path,const partial_range_t<const file_extension_t * > exts)507 PHYSFSX_uncounted_list PHYSFSX_findFiles(const char *path, const partial_range_t<const file_extension_t *> exts)
508 {
509 	const auto predicate = [&](const char *i) {
510 		return PHYSFSX_checkMatchingExtension(i, exts);
511 	};
512 	return PHYSFSX_findPredicateFiles(path, predicate);
513 }
514 
515 // Same function as above but takes a real directory as second argument, only adding files originating from this directory.
516 // This can be used to further seperate files in search path but it must be made sure realpath is properly formatted.
PHYSFSX_findabsoluteFiles(const char * path,const char * realpath,const partial_range_t<const file_extension_t * > exts)517 PHYSFSX_uncounted_list PHYSFSX_findabsoluteFiles(const char *path, const char *realpath, const partial_range_t<const file_extension_t *> exts)
518 {
519 	const auto predicate = [&](const char *i) {
520 		return PHYSFSX_checkMatchingExtension(i, exts) && (!strcmp(PHYSFS_getRealDir(i), realpath));
521 	};
522 	return PHYSFSX_findPredicateFiles(path, predicate);
523 }
524 
PHYSFSX_exists_ignorecase(const char * filename)525 int PHYSFSX_exists_ignorecase(const char *filename)
526 {
527 	char filename2[PATH_MAX];
528 	snprintf(filename2, sizeof(filename2), "%s", filename);
529 	return !PHYSFSEXT_locateCorrectCase(filename2);
530 }
531 
532 //Open a file for reading, set up a buffer
PHYSFSX_openReadBuffered(const char * filename)533 std::pair<RAIIPHYSFS_File, PHYSFS_ErrorCode> PHYSFSX_openReadBuffered(const char *filename)
534 {
535 	PHYSFS_uint64 bufSize;
536 	char filename2[PATH_MAX];
537 #if 0
538 	if (filename[0] == '\x01')
539 	{
540 		//FIXME: don't look in dir, only in hogfile
541 		filename++;
542 	}
543 #endif
544 	snprintf(filename2, sizeof(filename2), "%s", filename);
545 	PHYSFSEXT_locateCorrectCase(filename2);
546 
547 	RAIIPHYSFS_File fp{PHYSFS_openRead(filename2)};
548 	if (!fp)
549 		return {nullptr, PHYSFS_getLastErrorCode()};
550 
551 	bufSize = PHYSFS_fileLength(fp);
552 	while (!PHYSFS_setBuffer(fp, bufSize) && bufSize)
553 		bufSize /= 2;	// even if the error isn't memory full, for a 20MB file it'll only do this 8 times
554 	return {std::move(fp), PHYSFS_ERR_OK};
555 }
556 
557 //Open a file for writing, set up a buffer
PHYSFSX_openWriteBuffered(const char * filename)558 std::pair<RAIIPHYSFS_File, PHYSFS_ErrorCode> PHYSFSX_openWriteBuffered(const char *filename)
559 {
560 	PHYSFS_uint64 bufSize = 1024*1024;	// hmm, seems like an OK size.
561 
562 	RAIIPHYSFS_File fp{PHYSFS_openWrite(filename)};
563 	if (!fp)
564 		return {nullptr, PHYSFS_getLastErrorCode()};
565 	while (!PHYSFS_setBuffer(fp, bufSize) && bufSize)
566 		bufSize /= 2;
567 	return {std::move(fp), PHYSFS_ERR_OK};
568 }
569 
570 /*
571  * Add archives to the game.
572  * 1) archives from Sharepath/Data to extend/replace builtin game content
573  * 2) archived demos
574  */
PHYSFSX_addArchiveContent()575 void PHYSFSX_addArchiveContent()
576 {
577 	int content_updated = 0;
578 
579 	con_puts(CON_DEBUG, "PHYSFS: Adding archives to the game.");
580 	// find files in Searchpath ...
581 	auto list = PHYSFSX_findFiles("", archive_exts);
582 	// if found, add them...
583 	range_for (const auto i, list)
584 	{
585 		std::array<char, PATH_MAX> realfile;
586 		PHYSFSX_getRealPath(i,realfile);
587 		if (PHYSFS_mount(realfile.data(), nullptr, 0))
588 		{
589 			con_printf(CON_DEBUG, "PHYSFS: Added %s to Search Path",realfile.data());
590 			content_updated = 1;
591 		}
592 	}
593 #if PHYSFS_VER_MAJOR >= 2
594 	list.reset();
595 	// find files in DEMO_DIR ...
596 	list = PHYSFSX_findFiles(DEMO_DIR, archive_exts);
597 	// if found, add them...
598 	range_for (const auto i, list)
599 	{
600 		char demofile[PATH_MAX];
601 		snprintf(demofile, sizeof(demofile), DEMO_DIR "%s", i);
602 		std::array<char, PATH_MAX> realfile;
603 		PHYSFSX_getRealPath(demofile,realfile);
604 		if (PHYSFS_mount(realfile.data(), DEMO_DIR, 0))
605 		{
606 			con_printf(CON_DEBUG, "PHYSFS: Added %s to " DEMO_DIR, realfile.data());
607 			content_updated = 1;
608 		}
609 	}
610 #endif
611 	list.reset();
612 
613 	if (content_updated)
614 	{
615 		con_puts(CON_DEBUG, "Game content updated!");
616 		PHYSFSX_listSearchPathContent();
617 	}
618 }
619 
620 // Removes content added above when quitting game
PHYSFSX_removeArchiveContent()621 void PHYSFSX_removeArchiveContent()
622 {
623 	// find files in Searchpath ...
624 	auto list = PHYSFSX_findFiles("", archive_exts);
625 	// if found, remove them...
626 	range_for (const auto i, list)
627 	{
628 		std::array<char, PATH_MAX> realfile;
629 		PHYSFSX_getRealPath(i, realfile);
630 		PHYSFS_unmount(realfile.data());
631 	}
632 	list.reset();
633 	// find files in DEMO_DIR ...
634 	list = PHYSFSX_findFiles(DEMO_DIR, archive_exts);
635 	// if found, remove them...
636 	range_for (const auto i, list)
637 	{
638 		char demofile[PATH_MAX];
639 		snprintf(demofile, sizeof(demofile), DEMO_DIR "%s", i);
640 		std::array<char, PATH_MAX> realfile;
641 		PHYSFSX_getRealPath(demofile,realfile);
642 		PHYSFS_unmount(realfile.data());
643 	}
644 }
645 
PHYSFSX_read_helper_report_error(const char * const filename,const unsigned line,const char * const func,PHYSFS_File * const file)646 void PHYSFSX_read_helper_report_error(const char *const filename, const unsigned line, const char *const func, PHYSFS_File *const file)
647 {
648 	(Error)(filename, line, func, "reading at %lu", static_cast<unsigned long>((PHYSFS_tell)(file)));
649 }
650 
make_PHYSFSX_ComputedPathMount(const char * const name,physfs_search_path position)651 RAIIPHYSFS_ComputedPathMount make_PHYSFSX_ComputedPathMount(const char *const name, physfs_search_path position)
652 {
653 	auto pathname = std::make_unique<std::array<char, PATH_MAX>>();
654 	if (PHYSFSX_addRelToSearchPath(name, *pathname.get(), position) == PHYSFS_ERR_OK)
655 		return RAIIPHYSFS_ComputedPathMount(std::move(pathname));
656 	return nullptr;
657 }
658 
659 }
660