1 /*
2 
3 	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
4 	and the "Aleph One" developers.
5 
6 	This program is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 3 of the License, or
9 	(at your option) any later version.
10 
11 	This program is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 	GNU General Public License for more details.
15 
16 	This license is contained in the file "COPYING",
17 	which is included with this source code; it is available online at
18 	http://www.gnu.org/licenses/gpl.html
19 
20 */
21 
22 /*
23  *  FileHandler_SDL.cpp - Platform-independant file handling, SDL implementation
24  *
25  *  Written in 2000 by Christian Bauer
26  */
27 #include "cseries.h"
28 #include "FileHandler.h"
29 #include "resource_manager.h"
30 
31 #include "shell.h"
32 #include "interface.h"
33 #include "game_errors.h"
34 #include "tags.h"
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <errno.h>
39 #include <limits.h>
40 #include <string>
41 #include <vector>
42 
43 #include <SDL_endian.h>
44 
45 #ifdef HAVE_UNISTD_H
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #include <dirent.h>
49 #include <unistd.h>
50 #endif
51 
52 #ifdef HAVE_ZZIP
53 #include <zzip/lib.h>
54 #include "SDL_rwops_zzip.h"
55 #endif
56 
57 #if defined(__WIN32__)
58 #define PATH_SEP '\\'
59 #else
60 #define PATH_SEP '/'
61 #endif
62 
63 #include "sdl_dialogs.h"
64 #include "sdl_widgets.h"
65 #include "SoundManager.h" // !
66 
67 #include "preferences.h"
68 
69 #include <boost/bind.hpp>
70 #include <boost/function.hpp>
71 #include <boost/algorithm/string/predicate.hpp>
72 #include <boost/filesystem.hpp>
73 
74 namespace io = boost::iostreams;
75 
76 // From shell_sdl.cpp
77 extern vector<DirectorySpecifier> data_search_path;
78 extern DirectorySpecifier local_data_dir, preferences_dir, saved_games_dir, quick_saves_dir, image_cache_dir, recordings_dir;
79 
80 extern bool is_applesingle(SDL_RWops *f, bool rsrc_fork, int32 &offset, int32 &length);
81 extern bool is_macbinary(SDL_RWops *f, int32 &data_length, int32 &rsrc_length);
82 
83 /*
84  *  Opened file
85  */
86 
OpenedFile()87 OpenedFile::OpenedFile() : f(NULL), err(0), is_forked(false), fork_offset(0), fork_length(0) {}
88 
IsOpen()89 bool OpenedFile::IsOpen()
90 {
91 	return f != NULL;
92 }
93 
Close()94 bool OpenedFile::Close()
95 {
96 	if (f) {
97 		SDL_RWclose(f);
98 		f = NULL;
99 		err = 0;
100 	}
101 	is_forked = false;
102 	fork_offset = 0;
103 	fork_length = 0;
104 	return true;
105 }
106 
GetPosition(int32 & Position)107 bool OpenedFile::GetPosition(int32 &Position)
108 {
109 	if (f == NULL)
110 		return false;
111 
112 	err = 0;
113 	Position = SDL_RWtell(f) - fork_offset;
114 	return true;
115 }
116 
SetPosition(int32 Position)117 bool OpenedFile::SetPosition(int32 Position)
118 {
119 	if (f == NULL)
120 		return false;
121 
122 	err = 0;
123 	if (SDL_RWseek(f, Position + fork_offset, SEEK_SET) < 0)
124 		err = errno;
125 	return err == 0;
126 }
127 
GetLength(int32 & Length)128 bool OpenedFile::GetLength(int32 &Length)
129 {
130 	if (f == NULL)
131 		return false;
132 
133 	if (is_forked)
134 		Length = fork_length;
135 	else {
136 		int32 pos = SDL_RWtell(f);
137 		SDL_RWseek(f, 0, SEEK_END);
138 		Length = SDL_RWtell(f);
139 		SDL_RWseek(f, pos, SEEK_SET);
140 	}
141 	err = 0;
142 	return true;
143 }
144 
Read(int32 Count,void * Buffer)145 bool OpenedFile::Read(int32 Count, void *Buffer)
146 {
147 	if (f == NULL)
148 		return false;
149 
150 	err = 0;
151 	return (SDL_RWread(f, Buffer, 1, Count) == Count);
152 }
153 
Write(int32 Count,void * Buffer)154 bool OpenedFile::Write(int32 Count, void *Buffer)
155 {
156 	if (f == NULL)
157 		return false;
158 
159 	err = 0;
160 	return (SDL_RWwrite(f, Buffer, 1, Count) == Count);
161 }
162 
163 
TakeRWops()164 SDL_RWops *OpenedFile::TakeRWops ()
165 {
166 	SDL_RWops *taken = f;
167 	f = NULL;
168 	Close ();
169 	return taken;
170 }
171 
opened_file_device(OpenedFile & f)172 opened_file_device::opened_file_device(OpenedFile& f) : f(f) { }
173 
read(char * s,std::streamsize n)174 std::streamsize opened_file_device::read(char* s, std::streamsize n)
175 {
176 	return SDL_RWread(f.GetRWops(), s, 1, n);
177 }
178 
write(const char * s,std::streamsize n)179 std::streamsize opened_file_device::write(const char* s, std::streamsize n)
180 {
181 	return SDL_RWwrite(f.GetRWops(), s, 1, n);
182 }
183 
seek(io::stream_offset off,std::ios_base::seekdir way)184 std::streampos opened_file_device::seek(io::stream_offset off, std::ios_base::seekdir way)
185 {
186 	std::streampos pos;
187 
188 	switch (way)
189 	{
190 	case std::ios_base::beg:
191 		pos = SDL_RWseek(f.GetRWops(), off + f.fork_offset, SEEK_SET);
192 		break;
193 	case std::ios_base::end:
194 		pos = SDL_RWseek(f.GetRWops(), off, SEEK_END);
195 		break;
196 	case std::ios_base::cur:
197 		pos = SDL_RWseek(f.GetRWops(), off, SEEK_CUR);
198 		break;
199 	default:
200 		break;
201 	}
202 
203 	return pos - static_cast<std::streampos>(f.fork_offset);
204 }
205 
206 /*
207  *  Loaded resource
208  */
209 
LoadedResource()210 LoadedResource::LoadedResource() : p(NULL), size(0) {}
211 
IsLoaded()212 bool LoadedResource::IsLoaded()
213 {
214 	return p != NULL;
215 }
216 
Unload()217 void LoadedResource::Unload()
218 {
219 	if (p) {
220 		free(p);
221 		p = NULL;
222 		size = 0;
223 	}
224 }
225 
GetLength()226 size_t LoadedResource::GetLength()
227 {
228 	return size;
229 }
230 
GetPointer(bool DoDetach)231 void *LoadedResource::GetPointer(bool DoDetach)
232 {
233 	void *ret = p;
234 	if (DoDetach)
235 		Detach();
236 	return ret;
237 }
238 
SetData(void * data,size_t length)239 void LoadedResource::SetData(void *data, size_t length)
240 {
241 	Unload();
242 	p = data;
243 	size = length;
244 }
245 
Detach()246 void LoadedResource::Detach()
247 {
248 	p = NULL;
249 	size = 0;
250 }
251 
252 
253 /*
254  *  Opened resource file
255  */
256 
OpenedResourceFile()257 OpenedResourceFile::OpenedResourceFile() : f(NULL), saved_f(NULL), err(0) {}
258 
Push()259 bool OpenedResourceFile::Push()
260 {
261 	saved_f = cur_res_file();
262 	if (saved_f != f)
263 		use_res_file(f);
264 	err = 0;
265 	return true;
266 }
267 
Pop()268 bool OpenedResourceFile::Pop()
269 {
270 	if (f != saved_f)
271 		use_res_file(saved_f);
272 	err = 0;
273 	return true;
274 }
275 
Check(uint32 Type,int16 ID)276 bool OpenedResourceFile::Check(uint32 Type, int16 ID)
277 {
278 	Push();
279 	bool result = has_1_resource(Type, ID);
280 	err = result ? 0 : errno;
281 	Pop();
282 	return result;
283 }
284 
Get(uint32 Type,int16 ID,LoadedResource & Rsrc)285 bool OpenedResourceFile::Get(uint32 Type, int16 ID, LoadedResource &Rsrc)
286 {
287 	Push();
288 	bool success = get_1_resource(Type, ID, Rsrc);
289 	err = success ? 0 : errno;
290 	Pop();
291 	return success;
292 }
293 
IsOpen()294 bool OpenedResourceFile::IsOpen()
295 {
296 	return f != NULL;
297 }
298 
Close()299 bool OpenedResourceFile::Close()
300 {
301 	if (f) {
302 		close_res_file(f);
303 		f = NULL;
304 		err = 0;
305 	}
306 	return true;
307 }
308 
309 
310 /*
311  *  File specification
312  */
313 //AS: Constructor moved here to fix linking errors
FileSpecifier()314 FileSpecifier::FileSpecifier(): err(0) {}
operator =(const FileSpecifier & other)315 const FileSpecifier &FileSpecifier::operator=(const FileSpecifier &other)
316 {
317 	if (this != &other) {
318 		name = other.name;
319 		err = other.err;
320 	}
321 	return *this;
322 }
323 
324 // Create file
Create(Typecode Type)325 bool FileSpecifier::Create(Typecode Type)
326 {
327 	Delete();
328 	// files are automatically created when opened for writing
329 	err = 0;
330 	return true;
331 }
332 
333 // Create directory
CreateDirectory()334 bool FileSpecifier::CreateDirectory()
335 {
336 	err = 0;
337 #if defined(__WIN32__)
338 	if (mkdir(GetPath()) < 0)
339 #else
340 	if (mkdir(GetPath(), 0777) < 0)
341 #endif
342 		err = errno;
343 	return err == 0;
344 }
345 
346 #ifdef HAVE_ZZIP
unix_path_separators(const std::string & input)347 static std::string unix_path_separators(const std::string& input)
348 {
349 	if (PATH_SEP == '/') return input;
350 
351 	std::string output;
352 	for (std::string::const_iterator it = input.begin(); it != input.end(); ++it) {
353 		if (*it == PATH_SEP)
354 			output.push_back('/');
355 		else
356 			output.push_back(*it);
357 	}
358 
359 	return output;
360 }
361 #endif
362 
363 // Open data file
Open(OpenedFile & OFile,bool Writable)364 bool FileSpecifier::Open(OpenedFile &OFile, bool Writable)
365 {
366 	OFile.Close();
367 
368 	SDL_RWops *f;
369 	{
370 #ifdef HAVE_ZZIP
371 		if (!Writable)
372 		{
373 			f = OFile.f = SDL_RWFromZZIP(unix_path_separators(GetPath()).c_str(), "rb");
374 		}
375 		else {
376 			f = OFile.f = SDL_RWFromFile(GetPath(), "wb+");
377 		}
378 #else
379 		f = OFile.f = SDL_RWFromFile(GetPath(), Writable ? "wb+" : "rb");
380 #endif
381 
382 	}
383 
384 	err = f ? 0 : errno;
385 	if (f == NULL) {
386 		set_game_error(systemError, err);
387 		return false;
388 	}
389 	if (Writable)
390 		return true;
391 
392 	// Transparently handle AppleSingle and MacBinary files on reading
393 	int32 offset, data_length, rsrc_length;
394 	if (is_applesingle(f, false, offset, data_length)) {
395 		OFile.is_forked = true;
396 		OFile.fork_offset = offset;
397 		OFile.fork_length = data_length;
398 		SDL_RWseek(f, offset, SEEK_SET);
399 		return true;
400 	} else if (is_macbinary(f, data_length, rsrc_length)) {
401 		OFile.is_forked = true;
402 		OFile.fork_offset = 128;
403 		OFile.fork_length = data_length;
404 		SDL_RWseek(f, 128, SEEK_SET);
405 		return true;
406 	}
407 	SDL_RWseek(f, 0, SEEK_SET);
408 	return true;
409 }
410 
411 // Open resource file
Open(OpenedResourceFile & OFile,bool Writable)412 bool FileSpecifier::Open(OpenedResourceFile &OFile, bool Writable)
413 {
414 	OFile.Close();
415 
416 	OFile.f = open_res_file(*this);
417 	err = OFile.f ? 0 : errno;
418 	if (OFile.f == NULL) {
419 		set_game_error(systemError, err);
420 		return false;
421 	} else
422 		return true;
423 }
424 
425 // Check for existence of file
Exists()426 bool FileSpecifier::Exists()
427 {
428 	// Check whether the file is readable
429 	err = 0;
430 	if (access(GetPath(), R_OK) < 0)
431 		err = errno;
432 
433 #ifdef HAVE_ZZIP
434 	if (err)
435 	{
436 		// Check whether zzip can open the file (slow!)
437 		ZZIP_FILE* file = zzip_open(unix_path_separators(GetPath()).c_str(), R_OK);
438 		if (file)
439 		{
440 			zzip_close(file);
441 			return true;
442 		}
443 		else
444 		{
445 			return false;
446 		}
447 	}
448 #endif
449 	return (err == 0);
450 }
451 
IsDir()452 bool FileSpecifier::IsDir()
453 {
454 	struct stat st;
455 	err = 0;
456 	if (stat(GetPath(), &st) < 0)
457 		return false;
458 	return (S_ISDIR(st.st_mode));
459 }
460 
461 // Get modification date
GetDate()462 TimeType FileSpecifier::GetDate()
463 {
464 	struct stat st;
465 	err = 0;
466 	if (stat(GetPath(), &st) < 0) {
467 		err = errno;
468 		return 0;
469 	}
470 	return st.st_mtime;
471 }
472 
473 static const char * alephone_extensions[] = {
474 	".sceA",
475 	".sgaA",
476 	".filA",
477 	".phyA",
478 	".shpA",
479 	".sndA",
480 	0
481 };
482 
HideExtension(const std::string & filename)483 std::string FileSpecifier::HideExtension(const std::string& filename)
484 {
485 	if (environment_preferences->hide_extensions)
486 	{
487 		const char **extension = alephone_extensions;
488 		while (*extension)
489 		{
490 			if (boost::algorithm::ends_with(filename, *extension))
491 			{
492 				return filename.substr(0, filename.length() - strlen(*extension));
493 			}
494 
495 		++extension;
496 		}
497 	}
498 
499 	return filename;
500 }
501 
502 struct extension_mapping
503 {
504 	const char *extension;
505 	bool case_sensitive;
506 	Typecode typecode;
507 };
508 
509 static extension_mapping extensions[] =
510 {
511 	// some common extensions, to speed up building map lists
512 	{ "dds", false, _typecode_unknown },
513 	{ "jpg", false, _typecode_unknown },
514 	{ "png", false, _typecode_unknown },
515 	{ "bmp", false, _typecode_unknown },
516 	{ "txt", false, _typecode_unknown },
517 	{ "ttf", false, _typecode_unknown },
518 
519 	{ "lua", false, _typecode_netscript }, // netscript, or unknown?
520 	{ "mml", false, _typecode_unknown }, // no type code for this yet
521 
522 	{ "sceA", false, _typecode_scenario },
523 	{ "sgaA", false, _typecode_savegame },
524 	{ "filA", false, _typecode_film },
525 	{ "phyA", false, _typecode_physics },
526 	{ "ShPa", true,  _typecode_shapespatch }, // must come before shpA
527 	{ "shpA", false, _typecode_shapes },
528 	{ "sndA", false, _typecode_sounds },
529 
530 	{ "scen", false, _typecode_scenario },
531 	{ "shps", false, _typecode_shapes },
532 	{ "phys", false, _typecode_physics },
533 	{ "sndz", false, _typecode_sounds },
534 
535 	{ "mpg", false, _typecode_movie },
536 
537 	{0, false, _typecode_unknown}
538 };
539 
540 // Determine file type
GetType()541 Typecode FileSpecifier::GetType()
542 {
543 
544 	// if there's an extension, assume it's correct
545 	const char *extension = strrchr(GetPath(), '.');
546 	if (extension) {
547 		extension_mapping *mapping = extensions;
548 		while (mapping->extension)
549 		{
550 			if (( mapping->case_sensitive && (strcmp(extension + 1, mapping->extension) == 0)) ||
551 			    (!mapping->case_sensitive && (strcasecmp(extension + 1, mapping->extension) == 0)))
552 			{
553 				return mapping->typecode;
554 			}
555 			++mapping;
556 		}
557 	}
558 
559 	// Open file
560 	OpenedFile f;
561 	if (!Open(f))
562 		return _typecode_unknown;
563 	SDL_RWops *p = f.GetRWops();
564 	int32 file_length = 0;
565 	f.GetLength(file_length);
566 
567 	// Check for Sounds file
568 	{
569 		f.SetPosition(0);
570 		uint32 version = SDL_ReadBE32(p);
571 		uint32 tag = SDL_ReadBE32(p);
572 		if ((version == 0 || version == 1) && tag == FOUR_CHARS_TO_INT('s', 'n', 'd', '2'))
573 			return _typecode_sounds;
574 	}
575 
576 	// Check for Map/Physics file
577 	{
578 		f.SetPosition(0);
579 		int version = SDL_ReadBE16(p);
580 		int data_version = SDL_ReadBE16(p);
581 		if ((version == 0 || version == 1 || version == 2 || version == 4) && (data_version == 0 || data_version == 1 || data_version == 2)) {
582 			SDL_RWseek(p, 68, SEEK_CUR);
583 			int32 directory_offset = SDL_ReadBE32(p);
584 			if (directory_offset >= file_length)
585 				goto not_map;
586 			f.SetPosition(128);
587 			uint32 tag = SDL_ReadBE32(p);
588 			// ghs: I do not believe this list is comprehensive
589 			//      I think it's just what we've seen so far?
590 			switch (tag) {
591 			case LINE_TAG:
592 			case POINT_TAG:
593 			case SIDE_TAG:
594 				return _typecode_scenario;
595 				break;
596 			case MONSTER_PHYSICS_TAG:
597 				return _typecode_physics;
598 				break;
599 			}
600 
601 		}
602 not_map: ;
603 	}
604 
605 	// Check for Shapes file
606 	{
607 		f.SetPosition(0);
608 		for (int i=0; i<32; i++) {
609 			uint32 status_flags = SDL_ReadBE32(p);
610 			int32 offset = SDL_ReadBE32(p);
611 			int32 length = SDL_ReadBE32(p);
612 			int32 offset16 = SDL_ReadBE32(p);
613 			int32 length16 = SDL_ReadBE32(p);
614 			if (status_flags != 0
615 			 || (offset != NONE && (offset >= file_length || offset + length > file_length))
616 			 || (offset16 != NONE && (offset16 >= file_length || offset16 + length16 > file_length)))
617 				goto not_shapes;
618 			SDL_RWseek(p, 12, SEEK_CUR);
619 		}
620 		return _typecode_shapes;
621 not_shapes: ;
622 	}
623 
624 	// Not identified
625 	return _typecode_unknown;
626 }
627 
628 // Get free space on disk
GetFreeSpace(uint32 & FreeSpace)629 bool FileSpecifier::GetFreeSpace(uint32 &FreeSpace)
630 {
631 	// This is impossible to do in a platform-independant way, so we
632 	// just return 16MB which should be enough for everything
633 	FreeSpace = 16 * 1024 * 1024;
634 	err = 0;
635 	return true;
636 }
637 
638 // Delete file
Delete()639 bool FileSpecifier::Delete()
640 {
641 	err = 0;
642 	if (remove(GetPath()) < 0)
643 		err = errno;
644 	return err == 0;
645 }
646 
Rename(const FileSpecifier & Destination)647 bool FileSpecifier::Rename(const FileSpecifier& Destination)
648 {
649 #ifdef WIN32
650 	// Work around Windows' non-POSIX behavior on rename().
651 	// If we fail, go ahead and try to rename anyway.
652 	FileSpecifier d2 = Destination;
653 	if (d2.Exists() && !d2.IsDir())
654 		d2.Delete();
655 #endif
656 	return rename(GetPath(), Destination.GetPath()) == 0;
657 }
658 
659 // Set to local (per-user) data directory
SetToLocalDataDir()660 void FileSpecifier::SetToLocalDataDir()
661 {
662 	name = local_data_dir.name;
663 }
664 
665 // Set to preferences directory
SetToPreferencesDir()666 void FileSpecifier::SetToPreferencesDir()
667 {
668 	name = preferences_dir.name;
669 }
670 
671 // Set to saved games directory
SetToSavedGamesDir()672 void FileSpecifier::SetToSavedGamesDir()
673 {
674 	name = saved_games_dir.name;
675 }
676 
677 // Set to newer saved games directory
SetToQuickSavesDir()678 void FileSpecifier::SetToQuickSavesDir()
679 {
680 	name = quick_saves_dir.name;
681 }
682 
683 // Set to image cache directory
SetToImageCacheDir()684 void FileSpecifier::SetToImageCacheDir()
685 {
686 	name = image_cache_dir.name;
687 }
688 
689 // Set to recordings directory
SetToRecordingsDir()690 void FileSpecifier::SetToRecordingsDir()
691 {
692 	name = recordings_dir.name;
693 }
694 
local_path_separators(const char * path)695 static string local_path_separators(const char *path)
696 {
697 	string local_path = path;
698 	if (PATH_SEP == '/') return local_path;
699 
700 	for (size_t k = 0; k  < local_path.size(); ++k) {
701 		if (local_path[k] == '/')
702 			local_path[k] = PATH_SEP;
703 	}
704 
705 	return local_path;
706 }
707 
708 // Traverse search path, look for file given relative path name
SetNameWithPath(const char * NameWithPath)709 bool FileSpecifier::SetNameWithPath(const char *NameWithPath)
710 {
711 	if (*NameWithPath == '\0') {
712 		err = ENOENT;
713 		return false;
714 	}
715 
716 	FileSpecifier full_path;
717 	string rel_path = local_path_separators(NameWithPath);
718 
719 	vector<DirectorySpecifier>::const_iterator i = data_search_path.begin(), end = data_search_path.end();
720 	while (i != end) {
721 		full_path = *i + rel_path;
722 		if (full_path.Exists()) {
723 			name = full_path.name;
724 			err = 0;
725 			return true;
726 		}
727 		i++;
728 	}
729 	err = ENOENT;
730 	return false;
731 }
732 
SetNameWithPath(const char * NameWithPath,const DirectorySpecifier & Directory)733 bool FileSpecifier::SetNameWithPath(const char* NameWithPath, const DirectorySpecifier& Directory)
734 {
735 	if (*NameWithPath == '\0') {
736 		err = ENOENT;
737 		return false;
738 	}
739 
740 	FileSpecifier full_path;
741 	string rel_path = local_path_separators(NameWithPath);
742 
743 	full_path = Directory + rel_path;
744 	if (full_path.Exists()) {
745 		name = full_path.name;
746 		err = 0;
747 		return true;
748 	}
749 
750 	err = ENOENT;
751 	return false;
752 }
753 
SetTempName(const FileSpecifier & other)754 void FileSpecifier::SetTempName(const FileSpecifier& other)
755 {
756 	name = boost::filesystem::unique_path(other.name + "%%%%%%").string();
757 }
758 
759 // Get last element of path
GetName(char * part) const760 void FileSpecifier::GetName(char *part) const
761 {
762 	string::size_type pos = name.rfind(PATH_SEP);
763 	if (pos == string::npos)
764 		strcpy(part, name.c_str());
765 	else
766 		strcpy(part, name.substr(pos + 1).c_str());
767 }
768 
769 // Add part to path name
AddPart(const string & part)770 void FileSpecifier::AddPart(const string &part)
771 {
772 	if (name.length() && name[name.length() - 1] == PATH_SEP)
773 		name += local_path_separators(part.c_str());
774 	else
775 		name = name + PATH_SEP + local_path_separators(part.c_str());
776 
777 	canonicalize_path();
778 }
779 
780 // Split path to base and last part
SplitPath(string & base,string & part) const781 void FileSpecifier::SplitPath(string &base, string &part) const
782 {
783 	string::size_type pos = name.rfind(PATH_SEP);
784 	if (pos == string::npos) {
785 		base = name;
786 		part.erase();
787 	} else if (pos == 0) {
788 		base = PATH_SEP;
789 		part = name.substr(1);
790 	} else {
791 		base = name.substr(0, pos);
792 		part = name.substr(pos + 1);
793 	}
794 }
795 
796 // Fill file specifier with base name
ToDirectory(DirectorySpecifier & dir)797 void FileSpecifier::ToDirectory(DirectorySpecifier &dir)
798 {
799 	string part;
800 	SplitPath(dir, part);
801 }
802 
803 // Set file specifier from directory specifier
FromDirectory(DirectorySpecifier & Dir)804 void FileSpecifier::FromDirectory(DirectorySpecifier &Dir)
805 {
806 	name = Dir.name;
807 }
808 
809 // Canonicalize path
canonicalize_path(void)810 void FileSpecifier::canonicalize_path(void)
811 {
812 #if !defined(__WIN32__)
813 
814 	// Replace multiple consecutive '/'s by a single '/'
815 	while (true) {
816 		string::size_type pos = name.find("//");
817 		if (pos == string::npos)
818 			break;
819 		name.erase(pos, 1);
820 	}
821 
822 #endif
823 
824 	// Remove trailing '/'
825 	// ZZZ: only if we're not naming the root directory /
826 	if (!name.empty() && name[name.size()-1] == PATH_SEP && name.size() != 1)
827 		name.erase(name.size()-1, 1);
828 }
829 
830 // Read directory contents
ReadDirectory(vector<dir_entry> & vec)831 bool FileSpecifier::ReadDirectory(vector<dir_entry> &vec)
832 {
833 	vec.clear();
834 
835 	DIR *d = opendir(GetPath());
836 
837 	if (d == NULL) {
838 		err = errno;
839 		return false;
840 	}
841 	struct dirent *de = readdir(d);
842 	while (de) {
843 		FileSpecifier full_path = name;
844 		full_path += de->d_name;
845 		struct stat st;
846 		if (stat(full_path.GetPath(), &st) == 0) {
847 			// Ignore files starting with '.' and the directories '.' and '..'
848 			if (de->d_name[0] != '.' || (S_ISDIR(st.st_mode) && !(de->d_name[1] == '\0' || de->d_name[1] == '.')))
849 				vec.push_back(dir_entry(de->d_name, st.st_size, S_ISDIR(st.st_mode), false, st.st_mtime));
850 		}
851 		de = readdir(d);
852 	}
853 	closedir(d);
854 	err = 0;
855 	return true;
856 }
857 
858 // Copy file contents
CopyContents(FileSpecifier & source_name)859 bool FileSpecifier::CopyContents(FileSpecifier &source_name)
860 {
861 	err = 0;
862 	OpenedFile src, dst;
863 	if (source_name.Open(src)) {
864 		Delete();
865 		if (Open(dst, true)) {
866 			const int BUFFER_SIZE = 1024;
867 			uint8 buffer[BUFFER_SIZE];
868 
869 			int32 length = 0;
870 			src.GetLength(length);
871 
872 			while (length && err == 0) {
873 				int32 count = length > BUFFER_SIZE ? BUFFER_SIZE : length;
874 				if (src.Read(count, buffer)) {
875 					if (!dst.Write(count, buffer))
876 						err = dst.GetError();
877 				} else
878 					err = src.GetError();
879 				length -= count;
880 			}
881 		}
882 	} else
883 		err = source_name.GetError();
884 	if (err)
885 		Delete();
886 	return err == 0;
887 }
888 
889 // ZZZ: Filesystem browsing list that lets user actually navigate directories...
890 class w_directory_browsing_list : public w_list<dir_entry>
891 {
892 public:
w_directory_browsing_list(const FileSpecifier & inStartingDirectory,dialog * inParentDialog)893 	w_directory_browsing_list(const FileSpecifier& inStartingDirectory, dialog* inParentDialog)
894 		: w_list<dir_entry>(entries, 400, 15, 0), parent_dialog(inParentDialog), current_directory(inStartingDirectory), sort_order(sort_by_name)
895 	{
896 		refresh_entries();
897 	}
898 
899 
w_directory_browsing_list(const FileSpecifier & inStartingDirectory,dialog * inParentDialog,const string & inStartingFile)900 	w_directory_browsing_list(const FileSpecifier& inStartingDirectory, dialog* inParentDialog, const string& inStartingFile)
901 	: w_list<dir_entry>(entries, 400, 15, 0), parent_dialog(inParentDialog), current_directory(inStartingDirectory)
902 	{
903 		refresh_entries();
904 		if(entries.size() != 0)
905 			select_entry(inStartingFile, false);
906 	}
907 
908 
set_directory_changed_callback(action_proc inCallback,void * inArg=NULL)909 	void set_directory_changed_callback(action_proc inCallback, void* inArg = NULL)
910 	{
911 		directory_changed_proc = inCallback;
912 		directory_changed_proc_arg = inArg;
913 	}
914 
915 
draw_item(vector<dir_entry>::const_iterator i,SDL_Surface * s,int16 x,int16 y,uint16 width,bool selected) const916 	void draw_item(vector<dir_entry>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const
917 	{
918 		y += font->get_ascent();
919 		set_drawing_clip_rectangle(0, x, s->h, x + width);
920 
921 		if(i->is_directory)
922 		{
923 			string theName = i->name + "/";
924 			draw_text(s, theName.c_str (), x, y, selected ? get_theme_color (ITEM_WIDGET, ACTIVE_STATE) : get_theme_color (ITEM_WIDGET, DEFAULT_STATE), font, style, true);
925 		}
926 		else
927 		{
928 			char date[256];
929 			tm *time_info = localtime(&i->date);
930 
931 			if (time_info)
932 			{
933 				strftime(date, 256, "%x %H:%M", time_info);
934 				int date_width = text_width(date, font, style);
935 				draw_text(s, date, x + width - date_width, y, selected ? get_theme_color(ITEM_WIDGET, ACTIVE_STATE) : get_theme_color(ITEM_WIDGET, DEFAULT_STATE), font, style);
936 				set_drawing_clip_rectangle(0, x, s->h, x + width - date_width - 4);
937 			}
938 			draw_text(s, FileSpecifier::HideExtension(i->name).c_str (), x, y, selected ? get_theme_color (ITEM_WIDGET, ACTIVE_STATE) : get_theme_color (ITEM_WIDGET, DEFAULT_STATE), font, style, true);
939 		}
940 
941 		set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
942 	}
943 
944 
can_move_up_a_level()945 	bool can_move_up_a_level()
946 	{
947 		string base;
948 		string part;
949 		current_directory.SplitPath(base, part);
950 		return (part != string());
951 	}
952 
953 
move_up_a_level()954 	void move_up_a_level()
955 	{
956 		string base;
957 		string part;
958 		current_directory.SplitPath(base, part);
959 		if(part != string())
960 		{
961 			FileSpecifier parent_directory(base);
962 			if(parent_directory.Exists())
963 			{
964 				current_directory = parent_directory;
965 				refresh_entries();
966 				select_entry(part, true);
967 				announce_directory_changed();
968 			}
969 		}
970 	}
971 
972 
item_selected(void)973 	void item_selected(void)
974 	{
975 		current_directory.AddPart(entries[selection].name);
976 
977 		if(entries[selection].is_directory)
978 		{
979 			refresh_entries();
980 			announce_directory_changed();
981 		}
982 		else if (file_selected)
983 		{
984 			file_selected(entries[selection].name);
985 		}
986 	}
987 
988 	enum SortOrder {
989 		sort_by_name,
990 		sort_by_date,
991 	};
992 
sort_by(SortOrder order)993 	void sort_by(SortOrder order)
994 	{
995 		sort_order = order;
996 		refresh_entries();
997 	}
998 
999 
get_file()1000 	const FileSpecifier& get_file() { return current_directory; }
1001 
1002 	boost::function<void(const std::string&)> file_selected;
1003 
1004 private:
1005 	vector<dir_entry>	entries;
1006 	dialog*			parent_dialog;
1007 	FileSpecifier 		current_directory;
1008 	action_proc		directory_changed_proc;
1009 	void* directory_changed_proc_arg = nullptr;
1010 	SortOrder sort_order = sort_by_name;
1011 
1012 	struct most_recent
1013 	{
operator ()w_directory_browsing_list::most_recent1014 		bool operator()(const dir_entry& a, const dir_entry& b)
1015 		{
1016 			return a.date > b.date;
1017 		}
1018 	};
1019 
refresh_entries()1020 	void refresh_entries()
1021 	{
1022 		if(current_directory.ReadDirectory(entries))
1023 		{
1024 			if (sort_order == sort_by_name)
1025 			{
1026 				sort(entries.begin(), entries.end());
1027 			}
1028 			else
1029 			{
1030 				sort(entries.begin(), entries.end(), most_recent());
1031 			}
1032 		}
1033 		num_items = entries.size();
1034 		new_items();
1035 	}
1036 
select_entry(const string & inName,bool inIsDirectory)1037 	void select_entry(const string& inName, bool inIsDirectory)
1038 	{
1039 		dir_entry theEntryToFind(inName, NONE /* length - ignored for our purpose */, inIsDirectory);
1040 		vector<dir_entry>::iterator theEntry = find(entries.begin(), entries.end(), theEntryToFind);
1041 		if(theEntry != entries.end())
1042 			set_selection(theEntry - entries.begin());
1043 	}
1044 
announce_directory_changed()1045 	void announce_directory_changed()
1046 	{
1047 		if(directory_changed_proc != NULL)
1048 			directory_changed_proc(directory_changed_proc_arg);
1049 	}
1050 };
1051 
1052 const char* sort_by_labels[] = {
1053 	"name",
1054 	"date",
1055 	0
1056 };
1057 
1058 // common functionality for read and write dialogs
1059 class FileDialog {
1060 public:
FileDialog()1061 	FileDialog() {
1062 	}
1063 	virtual ~FileDialog() = default;
1064 
Run()1065 	bool Run() {
1066 		Layout();
1067 
1068 		bool result = false;
1069 		if (m_dialog.run() == 0)
1070 		{
1071 			result = true;
1072 		}
1073 
1074 		if (get_game_state() == _game_in_progress) update_game_window();
1075 		return result;
1076 	}
1077 
1078 protected:
Init(const FileSpecifier & dir,w_directory_browsing_list::SortOrder default_order,std::string filename)1079 	void Init(const FileSpecifier& dir, w_directory_browsing_list::SortOrder default_order, std::string filename) {
1080 		m_sort_by_w = new w_select(static_cast<size_t>(default_order), sort_by_labels);
1081 		m_sort_by_w->set_selection_changed_callback(boost::bind(&FileDialog::on_change_sort_order, this));
1082 		m_up_button_w = new w_button("UP", boost::bind(&FileDialog::on_up, this));
1083 		if (filename.empty())
1084 		{
1085 			m_list_w = new w_directory_browsing_list(dir, &m_dialog);
1086 		}
1087 		else
1088 		{
1089 			m_list_w = new w_directory_browsing_list(dir, &m_dialog, filename);
1090 		}
1091 		m_list_w->sort_by(default_order);
1092 		m_list_w->set_directory_changed_callback(boost::bind(&FileDialog::on_directory_changed, this));
1093 
1094 		dir.GetName(temporary);
1095 		m_directory_name_w = new w_static_text(temporary);
1096 	}
1097 
1098 	dialog m_dialog;
1099 	w_select* m_sort_by_w;
1100 	w_button* m_up_button_w;
1101 	w_static_text* m_directory_name_w;
1102 	w_directory_browsing_list* m_list_w;
1103 
1104 private:
1105 	virtual void Layout() = 0;
1106 
1107 
on_directory_changed()1108 	void on_directory_changed() {
1109 		m_list_w->get_file().GetName(temporary);
1110 		m_directory_name_w->set_text(temporary);
1111 
1112 		m_up_button_w->set_enabled(m_list_w->can_move_up_a_level());
1113 
1114 		m_dialog.draw();
1115 	}
1116 
on_change_sort_order()1117 	void on_change_sort_order() {
1118 		m_list_w->sort_by(static_cast<w_directory_browsing_list::SortOrder>(m_sort_by_w->get_selection()));
1119 	}
1120 
on_up()1121 	void on_up() {
1122 		m_list_w->move_up_a_level();
1123 	}
1124 
1125 };
1126 
1127 class ReadFileDialog : public FileDialog
1128 {
1129 public:
ReadFileDialog(FileSpecifier dir,Typecode type,const char * prompt)1130 	ReadFileDialog(FileSpecifier dir, Typecode type, const char* prompt) : FileDialog(), m_prompt(prompt) {
1131 		w_directory_browsing_list::SortOrder default_order = w_directory_browsing_list::sort_by_name;
1132 
1133 		if (!m_prompt)
1134 		{
1135 			switch(type)
1136 			{
1137 			case _typecode_savegame:
1138 				m_prompt = "CONTINUE SAVED GAME";
1139 				break;
1140 			case _typecode_film:
1141 				m_prompt = "REPLAY SAVED FILM";
1142 				break;
1143 			default:
1144 				m_prompt = "OPEN FILE";
1145 				break;
1146 			}
1147 		}
1148 
1149 		std::string filename;
1150 		switch (type)
1151 		{
1152 		case _typecode_savegame:
1153 			dir.SetToSavedGamesDir();
1154 			default_order = w_directory_browsing_list::sort_by_date;
1155 			break;
1156 		case _typecode_film:
1157 			dir.SetToRecordingsDir();
1158 			break;
1159 		case _typecode_scenario:
1160 		case _typecode_netscript:
1161 		{
1162 			// Go to most recently-used directory
1163 			DirectorySpecifier theDirectory;
1164 			dir.SplitPath(theDirectory, filename);
1165 			dir.FromDirectory(theDirectory);
1166 			if (!dir.Exists())
1167 				dir.SetToLocalDataDir();
1168 		}
1169 		break;
1170 		default:
1171 			dir.SetToLocalDataDir();
1172 			break;
1173 		}
1174 
1175 		Init(dir, default_order, filename);
1176 
1177 		m_list_w->file_selected = boost::bind(&ReadFileDialog::on_file_selected, this);
1178 	}
1179 	virtual ~ReadFileDialog() = default;
Layout()1180 	void Layout() {
1181 		vertical_placer* placer = new vertical_placer;
1182 		placer->dual_add(new w_title(m_prompt), m_dialog);
1183 		placer->add(new w_spacer, true);
1184 
1185 #ifndef MAC_APP_STORE
1186 		placer->dual_add(m_directory_name_w, m_dialog);
1187 
1188 		placer->add(new w_spacer(), true);
1189 #endif
1190 
1191 		horizontal_placer* top_row_placer = new horizontal_placer;
1192 
1193 		top_row_placer->dual_add(m_sort_by_w->label("Sorted by: "), m_dialog);
1194 		top_row_placer->dual_add(m_sort_by_w, m_dialog);
1195 		top_row_placer->add_flags(placeable::kFill);
1196 		top_row_placer->add(new w_spacer, true);
1197 		top_row_placer->add_flags();
1198 #ifndef MAC_APP_STORE
1199 		top_row_placer->dual_add(m_up_button_w, m_dialog);
1200 #endif
1201 
1202 		placer->add_flags(placeable::kFill);
1203 		placer->add(top_row_placer, true);
1204 		placer->add_flags();
1205 
1206 		placer->dual_add(m_list_w, m_dialog);
1207 		placer->add(new w_spacer, true);
1208 
1209 		horizontal_placer* button_placer = new horizontal_placer;
1210 		button_placer->dual_add(new w_button("CANCEL", dialog_cancel, &m_dialog), m_dialog);
1211 
1212 		placer->add(button_placer, true);
1213 
1214 		m_dialog.activate_widget(m_list_w);
1215 		m_dialog.set_widget_placer(placer);
1216 	}
1217 
GetFile()1218 	FileSpecifier GetFile() {
1219 		return m_list_w->get_file();
1220 	}
1221 
1222 private:
on_file_selected()1223 	void on_file_selected() {
1224 		m_dialog.quit(0);
1225 	}
1226 
1227 	const char* m_prompt;
1228 	std::string m_filename;
1229 };
1230 
ReadDialog(Typecode type,const char * prompt)1231 bool FileSpecifier::ReadDialog(Typecode type, const char *prompt)
1232 {
1233 	ReadFileDialog d(*this, type, prompt);
1234 	if (d.Run())
1235 	{
1236 		*this = d.GetFile();
1237 		return true;
1238 	}
1239 	else
1240 	{
1241 		return false;
1242 	}
1243 }
1244 
1245 class w_file_name : public w_text_entry {
1246 public:
w_file_name(dialog * d,const char * initial_name=NULL)1247 	w_file_name(dialog *d, const char *initial_name = NULL) : w_text_entry(31, initial_name), parent(d) {}
~w_file_name()1248 	~w_file_name() {}
1249 
event(SDL_Event & e)1250 	void event(SDL_Event & e)
1251 	{
1252 		// Return = close dialog
1253 		if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RETURN)
1254 			parent->quit(0);
1255 		w_text_entry::event(e);
1256 	}
1257 
1258 private:
1259 	dialog *parent;
1260 };
1261 
1262 class WriteFileDialog : public FileDialog
1263 {
1264 public:
WriteFileDialog(FileSpecifier dir,Typecode type,const char * prompt,const char * default_name)1265 	WriteFileDialog(FileSpecifier dir, Typecode type, const char* prompt, const char* default_name) : FileDialog(), m_prompt(prompt), m_default_name(default_name), m_extension(0) {
1266 		if (!m_prompt)
1267 		{
1268 			switch (type)
1269 			{
1270 			case _typecode_savegame:
1271 				m_prompt = "SAVE GAME";
1272 				break;
1273 			case _typecode_film:
1274 				m_prompt = "SAVE FILM";
1275 				break;
1276 			case _typecode_movie:
1277 				m_prompt = "EXPORT FILM";
1278 				break;
1279 			default:
1280 				m_prompt = "SAVE FILE";
1281 				break;
1282 			}
1283 		}
1284 
1285 		switch (type)
1286 		{
1287 		case _typecode_savegame:
1288 			m_extension = ".sgaA";
1289 			break;
1290 		case _typecode_film:
1291 			m_extension = ".filA";
1292 			break;
1293 		default:
1294 			break;
1295 		}
1296 
1297 		if (m_extension && boost::algorithm::ends_with(m_default_name, m_extension))
1298 		{
1299 			m_default_name.resize(m_default_name.size() - strlen(m_extension));
1300 		}
1301 
1302 		w_directory_browsing_list::SortOrder default_order = w_directory_browsing_list::sort_by_name;
1303 		switch (type) {
1304 		case _typecode_savegame:
1305 		{
1306 			string base;
1307 			string part;
1308 			dir.SplitPath(base, part);
1309 			if (part != string())
1310 			{
1311 				dir = base;
1312 			}
1313 			default_order = w_directory_browsing_list::sort_by_date;
1314 		}
1315 		break;
1316 		case _typecode_film:
1317 		case _typecode_movie:
1318 			dir.SetToRecordingsDir();
1319 			break;
1320 		default:
1321 			dir.SetToLocalDataDir();
1322 			break;
1323 		}
1324 
1325 		Init(dir, default_order, m_default_name);
1326 
1327 		m_list_w->file_selected = boost::bind(&WriteFileDialog::on_file_selected, this, _1);
1328 	}
1329 	virtual ~WriteFileDialog() = default;
Layout()1330 	void Layout() {
1331 		vertical_placer* placer = new vertical_placer;
1332 		placer->dual_add(new w_title(m_prompt), m_dialog);
1333 		placer->add(new w_spacer, true);
1334 
1335 #ifndef MAC_APP_STORE
1336 		placer->dual_add(m_directory_name_w, m_dialog);
1337 
1338 		placer->add(new w_spacer(), true);
1339 #endif
1340 
1341 		horizontal_placer* top_row_placer = new horizontal_placer;
1342 
1343 		top_row_placer->dual_add(m_sort_by_w->label("Sorted by: "), m_dialog);
1344 		top_row_placer->dual_add(m_sort_by_w, m_dialog);
1345 		top_row_placer->add_flags(placeable::kFill);
1346 		top_row_placer->add(new w_spacer, true);
1347 		top_row_placer->add_flags();
1348 #ifndef MAC_APP_STORE
1349 		top_row_placer->dual_add(m_up_button_w, m_dialog);
1350 #endif
1351 
1352 		placer->add_flags(placeable::kFill);
1353 		placer->add(top_row_placer, true);
1354 		placer->add_flags();
1355 
1356 		placer->dual_add(m_list_w, m_dialog);
1357 		placer->add(new w_spacer, true);
1358 
1359 		placer->add_flags(placeable::kFill);
1360 
1361 		horizontal_placer* file_name_placer = new horizontal_placer;
1362 		m_name_w = new w_file_name(&m_dialog, m_default_name.c_str());
1363 #ifdef MAC_APP_STORE
1364 		file_name_placer->dual_add(m_name_w->label("Name:"), m_dialog);
1365 #else
1366 		file_name_placer->dual_add(m_name_w->label("File Name:"), m_dialog);
1367 #endif
1368 		file_name_placer->add_flags(placeable::kFill);
1369 		file_name_placer->dual_add(m_name_w, m_dialog);
1370 
1371 		placer->add_flags(placeable::kFill);
1372 		placer->add(file_name_placer, true);
1373 		placer->add_flags();
1374 		placer->add(new w_spacer, true);
1375 
1376 		horizontal_placer* button_placer = new horizontal_placer;
1377 		button_placer->dual_add(new w_button("OK", dialog_ok, &m_dialog), m_dialog);
1378 		button_placer->dual_add(new w_button("CANCEL", dialog_cancel, &m_dialog), m_dialog);
1379 
1380 		placer->add(button_placer, true);
1381 
1382 		m_dialog.activate_widget(m_name_w);
1383 		m_dialog.set_widget_placer(placer);
1384 	}
1385 
GetPath()1386 	FileSpecifier GetPath() {
1387 		FileSpecifier dir = m_list_w->get_file();
1388 		std::string base;
1389 		std::string part;
1390 		dir.SplitPath(base, part);
1391 
1392 		std::string filename = GetFilename();
1393 		if (part == filename)
1394 		{
1395 			dir = base;
1396 		}
1397 
1398 		if (m_extension && !boost::algorithm::ends_with(filename, m_extension))
1399 		{
1400 			filename += m_extension;
1401 		}
1402 		dir.AddPart(filename);
1403 		return dir;
1404 	}
1405 
GetFilename()1406 	std::string GetFilename() {
1407 		return m_name_w->get_text();
1408 	}
1409 
1410 private:
on_file_selected(const std::string & filename)1411 	void on_file_selected(const std::string& filename) {
1412 		m_name_w->set_text(filename.c_str());
1413 		m_dialog.quit(0);
1414 	}
1415 
1416 	const char* m_prompt;
1417 	std::string m_default_name;
1418 	const char* m_extension;
1419 	w_file_name* m_name_w;
1420 };
1421 
1422 static bool confirm_save_choice(FileSpecifier & file);
1423 
WriteDialog(Typecode type,const char * prompt,const char * default_name)1424 bool FileSpecifier::WriteDialog(Typecode type, const char *prompt, const char *default_name)
1425 {
1426 again:
1427 	WriteFileDialog d(*this, type, prompt, default_name);
1428 	bool result = false;
1429 	if (d.Run())
1430 	{
1431 		if (d.GetFilename().empty())
1432 		{
1433 			play_dialog_sound(DIALOG_ERROR_SOUND);
1434 			goto again;
1435 		}
1436 
1437 		*this = d.GetPath();
1438 
1439 		if (!confirm_save_choice(*this))
1440 		{
1441 			goto again;
1442 		}
1443 
1444 		result = true;
1445 	}
1446 
1447 	return result;
1448 
1449 }
1450 
WriteDialogAsync(Typecode type,char * prompt,char * default_name)1451 bool FileSpecifier::WriteDialogAsync(Typecode type, char *prompt, char *default_name)
1452 {
1453 	return FileSpecifier::WriteDialog(type, prompt, default_name);
1454 }
1455 
confirm_save_choice(FileSpecifier & file)1456 static bool confirm_save_choice(FileSpecifier & file)
1457 {
1458 	// If the file doesn't exist, everything is alright
1459 	if (!file.Exists())
1460 		return true;
1461 
1462 	// Construct message
1463 	char name[256];
1464 	file.GetName(name);
1465 	char message[512];
1466 	sprintf(message, "'%s' already exists.", FileSpecifier::HideExtension(std::string(name)).c_str());
1467 
1468 	// Create dialog
1469 	dialog d;
1470 	vertical_placer *placer = new vertical_placer;
1471 	placer->dual_add(new w_static_text(message), d);
1472 	placer->dual_add(new w_static_text("Ok to overwrite?"), d);
1473 	placer->add(new w_spacer(), true);
1474 
1475 	horizontal_placer *button_placer = new horizontal_placer;
1476 	w_button *default_button = new w_button("YES", dialog_ok, &d);
1477 	button_placer->dual_add(default_button, d);
1478 	button_placer->dual_add(new w_button("NO", dialog_cancel, &d), d);
1479 
1480 	placer->add(button_placer, true);
1481 
1482 	d.activate_widget(default_button);
1483 
1484 	d.set_widget_placer(placer);
1485 
1486 	// Run dialog
1487 	return d.run() == 0;
1488 }
1489 
ScopedSearchPath(const DirectorySpecifier & dir)1490 ScopedSearchPath::ScopedSearchPath(const DirectorySpecifier& dir)
1491 {
1492 	data_search_path.insert(data_search_path.begin(), dir);
1493 }
1494 
~ScopedSearchPath()1495 ScopedSearchPath::~ScopedSearchPath()
1496 {
1497 	data_search_path.erase(data_search_path.begin());
1498 }
1499