Changeset - r24529:3dec691db49a
[Not reviewed]
master
0 16 0
Michael Lutz - 3 years ago 2020-12-06 20:11:50
michi@icosahedron.de
Codechange: Use std::string in file scanners.
16 files changed with 91 insertions and 132 deletions:
0 comments (0 inline, 0 general)
src/ai/ai_scanner.cpp
Show inline comments
 
@@ -11,50 +11,49 @@
 
#include "../debug.h"
 
#include "../network/network.h"
 
#include "../core/random_func.hpp"
 

	
 
#include "../script/squirrel_class.hpp"
 
#include "ai_info.hpp"
 
#include "ai_scanner.hpp"
 

	
 
#include "../safeguards.h"
 

	
 

	
 
AIScannerInfo::AIScannerInfo() :
 
	ScriptScanner(),
 
	info_dummy(nullptr)
 
{
 
}
 

	
 
void AIScannerInfo::Initialize()
 
{
 
	ScriptScanner::Initialize("AIScanner");
 

	
 
	ScriptAllocatorScope alloc_scope(this->engine);
 

	
 
	/* Create the dummy AI */
 
	free(this->main_script);
 
	this->main_script = stredup("%_dummy");
 
	this->main_script = "%_dummy";
 
	extern void Script_CreateDummyInfo(HSQUIRRELVM vm, const char *type, const char *dir);
 
	Script_CreateDummyInfo(this->engine->GetVM(), "AI", "ai");
 
}
 

	
 
void AIScannerInfo::SetDummyAI(class AIInfo *info)
 
{
 
	this->info_dummy = info;
 
}
 

	
 
AIScannerInfo::~AIScannerInfo()
 
{
 
	delete this->info_dummy;
 
}
 

	
 
void AIScannerInfo::GetScriptName(ScriptInfo *info, char *name, const char *last)
 
{
 
	seprintf(name, last, "%s", info->GetName());
 
}
 

	
 
void AIScannerInfo::RegisterAPI(class Squirrel *engine)
 
{
 
	AIInfo::RegisterAPI(engine);
 
}
 

	
src/base_media_base.h
Show inline comments
 
@@ -146,49 +146,49 @@ struct BaseSet {
 
	 */
 
	const char *GetTextfile(TextfileType type) const
 
	{
 
		for (uint i = 0; i < NUM_FILES; i++) {
 
			const char *textfile = ::GetTextfile(type, BASESET_DIR, this->files[i].filename);
 
			if (textfile != nullptr) {
 
				return textfile;
 
			}
 
		}
 
		return nullptr;
 
	}
 
};
 

	
 
/**
 
 * Base for all base media (graphics, sounds)
 
 * @tparam Tbase_set the real set we're going to be
 
 */
 
template <class Tbase_set>
 
class BaseMedia : FileScanner {
 
protected:
 
	static Tbase_set *available_sets; ///< All available sets
 
	static Tbase_set *duplicate_sets; ///< All sets that aren't available, but needed for not downloading base sets when a newer version than the one on BaNaNaS is loaded.
 
	static const Tbase_set *used_set; ///< The currently used set
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override;
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override;
 

	
 
	/**
 
	 * Get the extension that is used to identify this set.
 
	 * @return the extension
 
	 */
 
	static const char *GetExtension();
 
public:
 
	/** The set as saved in the config file. */
 
	static std::string ini_set;
 

	
 
	/**
 
	 * Determine the graphics pack that has to be used.
 
	 * The one with the most correct files wins.
 
	 * @return true if a best set has been found.
 
	 */
 
	static bool DetermineBestSet();
 

	
 
	/** Do the scan for files. */
 
	static uint FindSets()
 
	{
 
		BaseMedia<Tbase_set> fs;
 
		/* Searching in tars is only done in the old "data" directories basesets. */
 
		uint num = fs.Scan(GetExtension(), Tbase_set::SEARCH_IN_TARS ? OLD_DATA_DIR : OLD_GM_DIR, Tbase_set::SEARCH_IN_TARS);
 
		return num + fs.Scan(GetExtension(), BASESET_DIR, Tbase_set::SEARCH_IN_TARS);
src/base_media_func.h
Show inline comments
 
@@ -129,66 +129,66 @@ bool BaseSet<T, Tnum_files, Tsearch_in_t
 
		switch (file->check_result) {
 
			case MD5File::CR_UNKNOWN:
 
				break;
 

	
 
			case MD5File::CR_MATCH:
 
				this->valid_files++;
 
				this->found_files++;
 
				break;
 

	
 
			case MD5File::CR_MISMATCH:
 
				DEBUG(grf, 1, "MD5 checksum mismatch for: %s (in %s)", filename, full_filename);
 
				this->found_files++;
 
				break;
 

	
 
			case MD5File::CR_NO_FILE:
 
				DEBUG(grf, 1, "The file %s specified in %s is missing", filename, full_filename);
 
				break;
 
		}
 
	}
 

	
 
	return true;
 
}
 

	
 
template <class Tbase_set>
 
bool BaseMedia<Tbase_set>::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
bool BaseMedia<Tbase_set>::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
{
 
	bool ret = false;
 
	DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename);
 
	DEBUG(grf, 1, "Checking %s for base " SET_TYPE " set", filename.c_str());
 

	
 
	Tbase_set *set = new Tbase_set();
 
	IniFile *ini = new IniFile();
 
	std::string path{ filename + basepath_length };
 
	std::string path{ filename, basepath_length };
 
	ini->LoadFromDisk(path, BASESET_DIR);
 

	
 
	auto psep = path.rfind(PATHSEPCHAR);
 
	if (psep != std::string::npos) {
 
		path.erase(psep + 1);
 
	} else {
 
		path.clear();
 
	}
 

	
 
	if (set->FillSetDetails(ini, path.c_str(), filename)) {
 
	if (set->FillSetDetails(ini, path.c_str(), filename.c_str())) {
 
		Tbase_set *duplicate = nullptr;
 
		for (Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
 
			if (c->name == set->name || c->shortname == set->shortname) {
 
				duplicate = c;
 
				break;
 
			}
 
		}
 
		if (duplicate != nullptr) {
 
			/* The more complete set takes precedence over the version number. */
 
			if ((duplicate->valid_files == set->valid_files && duplicate->version >= set->version) ||
 
					duplicate->valid_files > set->valid_files) {
 
				DEBUG(grf, 1, "Not adding %s (%i) as base " SET_TYPE " set (duplicate, %s)", set->name.c_str(), set->version,
 
						duplicate->valid_files > set->valid_files ? "less valid files" : "lower version");
 
				set->next = BaseMedia<Tbase_set>::duplicate_sets;
 
				BaseMedia<Tbase_set>::duplicate_sets = set;
 
			} else {
 
				Tbase_set **prev = &BaseMedia<Tbase_set>::available_sets;
 
				while (*prev != duplicate) prev = &(*prev)->next;
 

	
 
				*prev = set;
 
				set->next = duplicate->next;
 

	
 
				/* If the duplicate set is currently used (due to rescanning this can happen)
 
				 * update the currently used set to the new one. This will 'lie' about the
 
@@ -356,36 +356,36 @@ template <class Tbase_set>
 
template <class Tbase_set>
 
/* static */ const Tbase_set *BaseMedia<Tbase_set>::GetUsedSet()
 
{
 
	return BaseMedia<Tbase_set>::used_set;
 
}
 

	
 
/**
 
 * Return the available sets.
 
 * @return The available sets.
 
 */
 
template <class Tbase_set>
 
/* static */ Tbase_set *BaseMedia<Tbase_set>::GetAvailableSets()
 
{
 
	return BaseMedia<Tbase_set>::available_sets;
 
}
 

	
 
/**
 
 * Force instantiation of methods so we don't get linker errors.
 
 * @param repl_type the type of the BaseMedia to instantiate
 
 * @param set_type  the type of the BaseSet to instantiate
 
 */
 
#define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type) \
 
	template std::string repl_type::ini_set; \
 
	template const char *repl_type::GetExtension(); \
 
	template bool repl_type::AddFile(const char *filename, size_t pathlength, const char *tar_filename); \
 
	template bool repl_type::AddFile(const std::string &filename, size_t pathlength, const std::string &tar_filename); \
 
	template bool repl_type::HasSet(const struct ContentInfo *ci, bool md5sum); \
 
	template bool repl_type::SetSet(const std::string &name); \
 
	template char *repl_type::GetSetsList(char *p, const char *last); \
 
	template int repl_type::GetNumSets(); \
 
	template int repl_type::GetIndexOfUsedSet(); \
 
	template const set_type *repl_type::GetSet(int index); \
 
	template const set_type *repl_type::GetUsedSet(); \
 
	template bool repl_type::DetermineBestSet(); \
 
	template set_type *repl_type::GetAvailableSets(); \
 
	template const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const set_type *s);
 

	
src/fileio.cpp
Show inline comments
 
@@ -225,49 +225,49 @@ static const char * const _subdirs[] = {
 
	"save" PATHSEP,
 
	"save" PATHSEP "autosave" PATHSEP,
 
	"scenario" PATHSEP,
 
	"scenario" PATHSEP "heightmap" PATHSEP,
 
	"gm" PATHSEP,
 
	"data" PATHSEP,
 
	"baseset" PATHSEP,
 
	"newgrf" PATHSEP,
 
	"lang" PATHSEP,
 
	"ai" PATHSEP,
 
	"ai" PATHSEP "library" PATHSEP,
 
	"game" PATHSEP,
 
	"game" PATHSEP "library" PATHSEP,
 
	"screenshot" PATHSEP,
 
};
 
static_assert(lengthof(_subdirs) == NUM_SUBDIRS);
 

	
 
/**
 
 * The search paths OpenTTD could search through.
 
 * At least one of the slots has to be filled with a path.
 
 * An empty string tells that there is no such path for the
 
 * current operating system.
 
 */
 
std::array<std::string, NUM_SEARCHPATHS> _searchpaths;
 
TarList _tar_list[NUM_SUBDIRS];
 
std::array<TarList, NUM_SUBDIRS> _tar_list;
 
TarFileList _tar_filelist[NUM_SUBDIRS];
 

	
 
typedef std::map<std::string, std::string> TarLinkList;
 
static TarLinkList _tar_linklist[NUM_SUBDIRS]; ///< List of directory links
 

	
 
/**
 
 * Checks whether the given search path is a valid search path
 
 * @param sp the search path to check
 
 * @return true if the search path is valid
 
 */
 
bool IsValidSearchPath(Searchpath sp)
 
{
 
	return sp < _searchpaths.size() && !_searchpaths[sp].empty();
 
}
 

	
 
/**
 
 * Check whether the given file exists
 
 * @param filename the file to try for existence.
 
 * @param subdir the subdirectory to look in
 
 * @return true if and only if the file can be opened
 
 */
 
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
 
{
 
	FILE *f = FioFOpenFile(filename, "rb", subdir);
 
@@ -369,49 +369,49 @@ static FILE *FioFOpenFileSp(const std::s
 
	f = fopen(buf.c_str(), mode);
 
#if !defined(_WIN32)
 
	if (f == nullptr && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
 
		f = fopen(buf.c_str(), mode);
 
	}
 
#endif
 
	if (f != nullptr && filesize != nullptr) {
 
		/* Find the size of the file */
 
		fseek(f, 0, SEEK_END);
 
		*filesize = ftell(f);
 
		fseek(f, 0, SEEK_SET);
 
	}
 
	return f;
 
}
 

	
 
/**
 
 * Opens a file from inside a tar archive.
 
 * @param entry The entry to open.
 
 * @param[out] filesize If not \c nullptr, size of the opened file.
 
 * @return File handle of the opened file, or \c nullptr if the file is not available.
 
 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
 
 */
 
FILE *FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
 
{
 
	FILE *f = fopen(entry.tar_filename, "rb");
 
	FILE *f = fopen(entry.tar_filename.c_str(), "rb");
 
	if (f == nullptr) return f;
 

	
 
	if (fseek(f, entry.position, SEEK_SET) < 0) {
 
		fclose(f);
 
		return nullptr;
 
	}
 

	
 
	if (filesize != nullptr) *filesize = entry.size;
 
	return f;
 
}
 

	
 
/**
 
 * Opens a OpenTTD file somewhere in a personal or global directory.
 
 * @param filename Name of the file to open.
 
 * @param subdir Subdirectory to open.
 
 * @return File handle of the opened file, or \c nullptr if the file is not available.
 
 */
 
FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
 
{
 
	FILE *f = nullptr;
 
	Searchpath sp;
 

	
 
	assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
 

	
 
@@ -592,150 +592,148 @@ uint TarScanner::DoScan(Subdirectory sd)
 
		num += fs.DoScan(NEWGRF_DIR);
 
	}
 
	if (mode & TarScanner::AI) {
 
		num += fs.DoScan(AI_DIR);
 
		num += fs.DoScan(AI_LIBRARY_DIR);
 
	}
 
	if (mode & TarScanner::GAME) {
 
		num += fs.DoScan(GAME_DIR);
 
		num += fs.DoScan(GAME_LIBRARY_DIR);
 
	}
 
	if (mode & TarScanner::SCENARIO) {
 
		num += fs.DoScan(SCENARIO_DIR);
 
		num += fs.DoScan(HEIGHTMAP_DIR);
 
	}
 
	DEBUG(misc, 1, "Scan complete, found %d files", num);
 
	return num;
 
}
 

	
 
/**
 
 * Add a single file to the scanned files of a tar, circumventing the scanning code.
 
 * @param sd       The sub directory the file is in.
 
 * @param filename The name of the file to add.
 
 * @return True if the additions went correctly.
 
 */
 
bool TarScanner::AddFile(Subdirectory sd, const char *filename)
 
bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
 
{
 
	this->subdir = sd;
 
	return this->AddFile(filename, 0);
 
}
 

	
 
bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
{
 
	/* No tar within tar. */
 
	assert(tar_filename == nullptr);
 
	assert(tar_filename.empty());
 

	
 
	/* The TAR-header, repeated for every file */
 
	struct TarHeader {
 
		char name[100];      ///< Name of the file
 
		char mode[8];
 
		char uid[8];
 
		char gid[8];
 
		char size[12];       ///< Size of the file, in ASCII
 
		char mtime[12];
 
		char chksum[8];
 
		char typeflag;
 
		char linkname[100];
 
		char magic[6];
 
		char version[2];
 
		char uname[32];
 
		char gname[32];
 
		char devmajor[8];
 
		char devminor[8];
 
		char prefix[155];    ///< Path of the file
 

	
 
		char unused[12];
 
	};
 

	
 
	/* Check if we already seen this file */
 
	TarList::iterator it = _tar_list[this->subdir].find(filename);
 
	if (it != _tar_list[this->subdir].end()) return false;
 

	
 
	FILE *f = fopen(filename, "rb");
 
	FILE *f = fopen(filename.c_str(), "rb");
 
	/* Although the file has been found there can be
 
	 * a number of reasons we cannot open the file.
 
	 * Most common case is when we simply have not
 
	 * been given read access. */
 
	if (f == nullptr) return false;
 

	
 
	const char *dupped_filename = stredup(filename);
 
	_tar_list[this->subdir][filename].filename = dupped_filename;
 
	_tar_list[this->subdir][filename].dirname = nullptr;
 
	_tar_list[this->subdir][filename] = std::string{};
 

	
 
	TarLinkList links; ///< Temporary list to collect links
 

	
 
	TarHeader th;
 
	char buf[sizeof(th.name) + 1], *end;
 
	char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
 
	char link[sizeof(th.linkname) + 1];
 
	char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
 
	size_t num = 0, pos = 0;
 

	
 
	/* Make a char of 512 empty bytes */
 
	char empty[512];
 
	memset(&empty[0], 0, sizeof(empty));
 

	
 
	for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
 
		size_t num_bytes_read = fread(&th, 1, 512, f);
 
		if (num_bytes_read != 512) break;
 
		pos += num_bytes_read;
 

	
 
		/* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
 
		if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
 
			/* If we have only zeros in the block, it can be an end-of-file indicator */
 
			if (memcmp(&th, &empty[0], 512) == 0) continue;
 

	
 
			DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
 
			DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename.c_str());
 
			fclose(f);
 
			return false;
 
		}
 

	
 
		name[0] = '\0';
 

	
 
		/* The prefix contains the directory-name */
 
		if (th.prefix[0] != '\0') {
 
			strecpy(name, th.prefix, lastof(name));
 
			strecat(name, PATHSEP, lastof(name));
 
		}
 

	
 
		/* Copy the name of the file in a safe way at the end of 'name' */
 
		strecat(name, th.name, lastof(name));
 

	
 
		/* Calculate the size of the file.. for some strange reason this is stored as a string */
 
		strecpy(buf, th.size, lastof(buf));
 
		size_t skip = strtoul(buf, &end, 8);
 

	
 
		switch (th.typeflag) {
 
			case '\0':
 
			case '0': { // regular file
 
				/* Ignore empty files */
 
				if (skip == 0) break;
 

	
 
				if (strlen(name) == 0) break;
 

	
 
				/* Store this entry in the list */
 
				TarFileListEntry entry;
 
				entry.tar_filename = dupped_filename;
 
				entry.tar_filename = filename;
 
				entry.size         = skip;
 
				entry.position     = pos;
 

	
 
				/* Convert to lowercase and our PATHSEPCHAR */
 
				SimplifyFileName(name);
 

	
 
				DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
 
				if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
 

	
 
				break;
 
			}
 

	
 
			case '1': // hard links
 
			case '2': { // symbolic links
 
				/* Copy the destination of the link in a safe way at the end of 'linkname' */
 
				strecpy(link, th.linkname, lastof(link));
 

	
 
				if (strlen(name) == 0 || strlen(link) == 0) break;
 

	
 
				/* Convert to lowercase and our PATHSEPCHAR */
 
				SimplifyFileName(name);
 
				SimplifyFileName(link);
 

	
 
				/* Only allow relative links */
 
@@ -761,125 +759,125 @@ bool TarScanner::AddFile(const char *fil
 
						*next++= '\0';
 
					}
 

	
 
					if (strcmp(pos, ".") == 0) {
 
						/* Skip '.' (current dir) */
 
					} else if (strcmp(pos, "..") == 0) {
 
						/* level up */
 
						if (dest[0] == '\0') {
 
							DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
 
							break;
 
						}
 

	
 
						/* Truncate 'dest' after last PATHSEPCHAR.
 
						 * This assumes that the truncated part is a real directory and not a link. */
 
						destpos = strrchr(dest, PATHSEPCHAR);
 
						if (destpos == nullptr) destpos = dest;
 
						*destpos = '\0';
 
					} else {
 
						/* Append at end of 'dest' */
 
						if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
 
						destpos = strecpy(destpos, pos, lastof(dest));
 
					}
 

	
 
					if (destpos >= lastof(dest)) {
 
						DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
 
						DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename.c_str());
 
						fclose(f);
 
						return false;
 
					}
 

	
 
					pos = next;
 
				}
 

	
 
				/* Store links in temporary list */
 
				DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
 
				links.insert(TarLinkList::value_type(name, dest));
 

	
 
				break;
 
			}
 

	
 
			case '5': // directory
 
				/* Convert to lowercase and our PATHSEPCHAR */
 
				SimplifyFileName(name);
 

	
 
				/* Store the first directory name we detect */
 
				DEBUG(misc, 6, "Found dir in tar: %s", name);
 
				if (_tar_list[this->subdir][filename].dirname == nullptr) _tar_list[this->subdir][filename].dirname = stredup(name);
 
				if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = name;
 
				break;
 

	
 
			default:
 
				/* Ignore other types */
 
				break;
 
		}
 

	
 
		/* Skip to the next block.. */
 
		skip = Align(skip, 512);
 
		if (fseek(f, skip, SEEK_CUR) < 0) {
 
			DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
 
			DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename.c_str());
 
			fclose(f);
 
			return false;
 
		}
 
		pos += skip;
 
	}
 

	
 
	DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
 
	DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename.c_str(), num);
 
	fclose(f);
 

	
 
	/* Resolve file links and store directory links.
 
	 * We restrict usage of links to two cases:
 
	 *  1) Links to directories:
 
	 *      Both the source path and the destination path must NOT contain any further links.
 
	 *      When resolving files at most one directory link is resolved.
 
	 *  2) Links to files:
 
	 *      The destination path must NOT contain any links.
 
	 *      The source path may contain one directory link.
 
	 */
 
	for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
 
		const std::string &src = link->first;
 
		const std::string &dest = link->second;
 
		TarAddLink(src, dest, this->subdir);
 
	}
 

	
 
	return true;
 
}
 

	
 
/**
 
 * Extract the tar with the given filename in the directory
 
 * where the tar resides.
 
 * @param tar_filename the name of the tar to extract.
 
 * @param subdir The sub directory the tar is in.
 
 * @return false on failure.
 
 */
 
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
 
{
 
	TarList::iterator it = _tar_list[subdir].find(tar_filename);
 
	/* We don't know the file. */
 
	if (it == _tar_list[subdir].end()) return false;
 

	
 
	const char *dirname = (*it).second.dirname;
 
	const auto &dirname = (*it).second;
 

	
 
	/* The file doesn't have a sub directory! */
 
	if (dirname == nullptr) {
 
	if (dirname.empty()) {
 
		DEBUG(misc, 1, "Extracting %s failed; archive rejected, the contents must be in a sub directory", tar_filename.c_str());
 
		return false;
 
	}
 

	
 
	std::string filename = tar_filename;
 
	auto p = filename.find_last_of(PATHSEPCHAR);
 
	/* The file's path does not have a separator? */
 
	if (p == std::string::npos) return false;
 

	
 
	filename.replace(p + 1, std::string::npos, dirname);
 
	DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename.c_str(), filename.c_str());
 
	FioCreateDirectory(filename);
 

	
 
	for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
 
		if (tar_filename != it2->second.tar_filename) continue;
 

	
 
		filename.replace(p + 1, std::string::npos, it2->first);
 

	
 
		DEBUG(misc, 9, "  extracting %s", filename.c_str());
 

	
 
		/* First open the file in the .tar. */
 
		size_t to_copy = 0;
 
		std::unique_ptr<FILE, FileDeleter> in(FioFOpenFileTar(it2->second, &to_copy));
 
		if (!in) {
 
@@ -1287,69 +1285,69 @@ static uint ScanPath(FileScanner *fs, co
 

	
 
	uint num = 0;
 
	struct stat sb;
 
	struct dirent *dirent;
 
	DIR *dir;
 

	
 
	if (path == nullptr || (dir = ttd_opendir(path)) == nullptr) return 0;
 

	
 
	while ((dirent = readdir(dir)) != nullptr) {
 
		const char *d_name = FS2OTTD(dirent->d_name);
 

	
 
		if (!FiosIsValidFile(path, dirent, &sb)) continue;
 

	
 
		std::string filename(path);
 
		filename += d_name;
 

	
 
		if (S_ISDIR(sb.st_mode)) {
 
			/* Directory */
 
			if (!recursive) continue;
 
			if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
 
			AppendPathSeparator(filename);
 
			num += ScanPath(fs, extension, filename.c_str(), basepath_length, recursive);
 
		} else if (S_ISREG(sb.st_mode)) {
 
			/* File */
 
			if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename.c_str(), basepath_length, nullptr)) num++;
 
			if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, basepath_length, {})) num++;
 
		}
 
	}
 

	
 
	closedir(dir);
 

	
 
	return num;
 
}
 

	
 
/**
 
 * Scan the given tar and add graphics sets when it finds one.
 
 * @param fs        the file scanner to scan for
 
 * @param extension the extension of files to search for.
 
 * @param tar       the tar to search in.
 
 */
 
static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
 
{
 
	uint num = 0;
 
	const char *filename = (*tar).first.c_str();
 
	const auto &filename = (*tar).first;
 

	
 
	if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
 
	if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
 

	
 
	return num;
 
}
 

	
 
/**
 
 * Scan for files with the given extension in the given search path.
 
 * @param extension the extension of files to search for.
 
 * @param sd        the sub directory to search in.
 
 * @param tars      whether to search in the tars too.
 
 * @param recursive whether to search recursively
 
 * @return the number of found files, i.e. the number of times that
 
 *         AddFile returned true.
 
 */
 
uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
 
{
 
	this->subdir = sd;
 

	
 
	Searchpath sp;
 
	TarFileList::iterator tar;
 
	uint num = 0;
 

	
 
	FOR_ALL_SEARCHPATHS(sp) {
 
		/* Don't search in the working directory */
 
		if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
src/fileio_func.h
Show inline comments
 
@@ -53,69 +53,69 @@ std::unique_ptr<char> ReadFileToMem(cons
 
bool FileExists(const std::string &filename);
 
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir);
 

	
 
extern std::string _personal_dir; ///< custom directory for personal settings, saves, newgrf, etc.
 

	
 
/** Helper for scanning for files with a given name */
 
class FileScanner {
 
protected:
 
	Subdirectory subdir; ///< The current sub directory we are searching through
 
public:
 
	/** Destruct the proper one... */
 
	virtual ~FileScanner() {}
 

	
 
	uint Scan(const char *extension, Subdirectory sd, bool tars = true, bool recursive = true);
 
	uint Scan(const char *extension, const char *directory, bool recursive = true);
 

	
 
	/**
 
	 * Add a file with the given filename.
 
	 * @param filename        the full path to the file to read
 
	 * @param basepath_length amount of characters to chop of before to get a
 
	 *                        filename relative to the search path.
 
	 * @param tar_filename    the name of the tar file the file is read from.
 
	 * @return true if the file is added.
 
	 */
 
	virtual bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) = 0;
 
	virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) = 0;
 
};
 

	
 
/** Helper for scanning for files with tar as extension */
 
class TarScanner : FileScanner {
 
	uint DoScan(Subdirectory sd);
 
public:
 
	/** The mode of tar scanning. */
 
	enum Mode {
 
		NONE     = 0,      ///< Scan nothing.
 
		BASESET  = 1 << 0, ///< Scan for base sets.
 
		NEWGRF   = 1 << 1, ///< Scan for non-base sets.
 
		AI       = 1 << 2, ///< Scan for AIs and its libraries.
 
		SCENARIO = 1 << 3, ///< Scan for scenarios and heightmaps.
 
		GAME     = 1 << 4, ///< Scan for game scripts.
 
		ALL      = BASESET | NEWGRF | AI | SCENARIO | GAME, ///< Scan for everything.
 
	};
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename = nullptr) override;
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename = nullptr) override;
 

	
 
	bool AddFile(Subdirectory sd, const char *filename);
 
	bool AddFile(Subdirectory sd, const std::string &filename);
 

	
 
	/** Do the scan for Tars. */
 
	static uint DoScan(TarScanner::Mode mode);
 
};
 

	
 
DECLARE_ENUM_AS_BIT_SET(TarScanner::Mode)
 

	
 
/* Implementation of opendir/readdir/closedir for Windows */
 
#if defined(_WIN32)
 
struct DIR;
 

	
 
struct dirent { // XXX - only d_name implemented
 
	TCHAR *d_name; // name of found file
 
	/* little hack which will point to parent DIR struct which will
 
	 * save us a call to GetFileAttributes if we want information
 
	 * about the file (for example in function fio_bla) */
 
	DIR *dir;
 
};
 

	
 
DIR *opendir(const TCHAR *path);
 
struct dirent *readdir(DIR *d);
 
int closedir(DIR *d);
 
#else
 
/* Use system-supplied opendir/readdir/closedir functions */
src/fios.cpp
Show inline comments
 
@@ -21,49 +21,49 @@
 
#include <sys/stat.h>
 
#include <functional>
 
#include <optional>
 

	
 
#ifndef _WIN32
 
# include <unistd.h>
 
#endif /* _WIN32 */
 

	
 
#include "table/strings.h"
 

	
 
#include "safeguards.h"
 

	
 
/* Variables to display file lists */
 
static std::string *_fios_path = nullptr;
 
SortingBits _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
 

	
 
/* OS-specific functions are taken from their respective files (win32/unix/os2 .c) */
 
extern bool FiosIsRoot(const char *path);
 
extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
 
extern bool FiosIsHiddenFile(const struct dirent *ent);
 
extern void FiosGetDrives(FileList &file_list);
 
extern bool FiosGetDiskFreeSpace(const char *path, uint64 *tot);
 

	
 
/* get the name of an oldstyle savegame */
 
extern void GetOldSaveGameName(const char *file, char *title, const char *last);
 
extern void GetOldSaveGameName(const std::string &file, char *title, const char *last);
 

	
 
/**
 
 * Compare two FiosItem's. Used with sort when sorting the file list.
 
 * @param other The FiosItem to compare to.
 
 * @return for ascending order: returns true if da < db. Vice versa for descending order.
 
 */
 
bool FiosItem::operator< (const FiosItem &other) const
 
{
 
	int r = false;
 

	
 
	if ((_savegame_sort_order & SORT_BY_NAME) == 0 && (*this).mtime != other.mtime) {
 
		r = (*this).mtime - other.mtime;
 
	} else {
 
		r = strnatcmp((*this).title, other.title);
 
	}
 
	if (r == 0) return false;
 
	return (_savegame_sort_order & SORT_DESCENDING) ? r > 0 : r < 0;
 
}
 

	
 
FileList::~FileList()
 
{
 
	this->Clear();
 
}
 

	
 
@@ -239,129 +239,130 @@ std::string FiosMakeSavegameName(const c
 
/**
 
 * Construct a filename for a height map.
 
 * @param name Filename.
 
 * @return The completed filename.
 
 */
 
std::string FiosMakeHeightmapName(const char *name)
 
{
 
	std::string ext(".");
 
	ext += GetCurrentScreenshotExtension();
 

	
 
	return FiosMakeFilename(_fios_path, name, ext.c_str());
 
}
 

	
 
/**
 
 * Delete a file.
 
 * @param name Filename to delete.
 
 * @return Whether the file deletion was successful.
 
 */
 
bool FiosDelete(const char *name)
 
{
 
	std::string filename = FiosMakeSavegameName(name);
 
	return unlink(filename.c_str()) == 0;
 
}
 

	
 
typedef FiosType fios_getlist_callback_proc(SaveLoadOperation fop, const char *filename, const char *ext, char *title, const char *last);
 
typedef FiosType fios_getlist_callback_proc(SaveLoadOperation fop, const std::string &filename, const char *ext, char *title, const char *last);
 

	
 
/**
 
 * Scanner to scan for a particular type of FIOS file.
 
 */
 
class FiosFileScanner : public FileScanner {
 
	SaveLoadOperation fop;   ///< The kind of file we are looking for.
 
	fios_getlist_callback_proc *callback_proc; ///< Callback to check whether the file may be added
 
	FileList &file_list;     ///< Destination of the found files.
 
public:
 
	/**
 
	 * Create the scanner
 
	 * @param fop Purpose of collecting the list.
 
	 * @param callback_proc The function that is called where you need to do the filtering.
 
	 * @param file_list Destination of the found files.
 
	 */
 
	FiosFileScanner(SaveLoadOperation fop, fios_getlist_callback_proc *callback_proc, FileList &file_list) :
 
			fop(fop), callback_proc(callback_proc), file_list(file_list)
 
	{}
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override;
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override;
 
};
 

	
 
/**
 
 * Try to add a fios item set with the given filename.
 
 * @param filename        the full path to the file to read
 
 * @param basepath_length amount of characters to chop of before to get a relative filename
 
 * @return true if the file is added.
 
 */
 
bool FiosFileScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
bool FiosFileScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
{
 
	const char *ext = strrchr(filename, '.');
 
	if (ext == nullptr) return false;
 
	auto sep = filename.rfind('.');
 
	if (sep == std::string::npos) return false;
 
	std::string ext = filename.substr(sep);
 

	
 
	char fios_title[64];
 
	fios_title[0] = '\0'; // reset the title;
 

	
 
	FiosType type = this->callback_proc(this->fop, filename, ext, fios_title, lastof(fios_title));
 
	FiosType type = this->callback_proc(this->fop, filename, ext.c_str(), fios_title, lastof(fios_title));
 
	if (type == FIOS_TYPE_INVALID) return false;
 

	
 
	for (const FiosItem *fios = file_list.Begin(); fios != file_list.End(); fios++) {
 
		if (strcmp(fios->name, filename) == 0) return false;
 
		if (filename == fios->name) return false;
 
	}
 

	
 
	FiosItem *fios = file_list.Append();
 
#ifdef _WIN32
 
	// Retrieve the file modified date using GetFileTime rather than stat to work around an obscure MSVC bug that affects Windows XP
 
	HANDLE fh = CreateFile(OTTD2FS(filename), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
 
	HANDLE fh = CreateFile(OTTD2FS(filename.c_str()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
 

	
 
	if (fh != INVALID_HANDLE_VALUE) {
 
		FILETIME ft;
 
		ULARGE_INTEGER ft_int64;
 

	
 
		if (GetFileTime(fh, nullptr, nullptr, &ft) != 0) {
 
			ft_int64.HighPart = ft.dwHighDateTime;
 
			ft_int64.LowPart = ft.dwLowDateTime;
 

	
 
			// Convert from hectonanoseconds since 01/01/1601 to seconds since 01/01/1970
 
			fios->mtime = ft_int64.QuadPart / 10000000ULL - 11644473600ULL;
 
		} else {
 
			fios->mtime = 0;
 
		}
 

	
 
		CloseHandle(fh);
 
#else
 
	struct stat sb;
 
	if (stat(filename, &sb) == 0) {
 
	if (stat(filename.c_str(), &sb) == 0) {
 
		fios->mtime = sb.st_mtime;
 
#endif
 
	} else {
 
		fios->mtime = 0;
 
	}
 

	
 
	fios->type = type;
 
	strecpy(fios->name, filename, lastof(fios->name));
 
	strecpy(fios->name, filename.c_str(), lastof(fios->name));
 

	
 
	/* If the file doesn't have a title, use its filename */
 
	const char *t = fios_title;
 
	if (StrEmpty(fios_title)) {
 
		t = strrchr(filename, PATHSEPCHAR);
 
		t = (t == nullptr) ? filename : (t + 1);
 
		auto ps = filename.rfind(PATHSEPCHAR);
 
		t = filename.c_str() + (ps == std::string::npos ? 0 : ps + 1);
 
	}
 
	strecpy(fios->title, t, lastof(fios->title));
 
	str_validate(fios->title, lastof(fios->title));
 

	
 
	return true;
 
}
 

	
 

	
 
/**
 
 * Fill the list of the files in a directory, according to some arbitrary rule.
 
 * @param fop Purpose of collecting the list.
 
 * @param callback_proc The function that is called where you need to do the filtering.
 
 * @param subdir The directory from where to start (global) searching.
 
 * @param file_list Destination of the found files.
 
 */
 
static void FiosGetFileList(SaveLoadOperation fop, fios_getlist_callback_proc *callback_proc, Subdirectory subdir, FileList &file_list)
 
{
 
	struct stat sb;
 
	struct dirent *dirent;
 
	DIR *dir;
 
	FiosItem *fios;
 
	size_t sort_start;
 
	char d_name[sizeof(fios->name)];
 

	
 
@@ -412,76 +413,75 @@ static void FiosGetFileList(SaveLoadOper
 
	/* Show files */
 
	FiosFileScanner scanner(fop, callback_proc, file_list);
 
	if (subdir == NO_DIRECTORY) {
 
		scanner.Scan(nullptr, _fios_path->c_str(), false);
 
	} else {
 
		scanner.Scan(nullptr, subdir, true, true);
 
	}
 

	
 
	std::sort(file_list.files.begin() + sort_start, file_list.files.end());
 

	
 
	/* Show drives */
 
	FiosGetDrives(file_list);
 

	
 
	file_list.Compact();
 
}
 

	
 
/**
 
 * Get the title of a file, which (if exists) is stored in a file named
 
 * the same as the data file but with '.title' added to it.
 
 * @param file filename to get the title for
 
 * @param title the title buffer to fill
 
 * @param last the last element in the title buffer
 
 * @param subdir the sub directory to search in
 
 */
 
static void GetFileTitle(const char *file, char *title, const char *last, Subdirectory subdir)
 
static void GetFileTitle(const std::string &file, char *title, const char *last, Subdirectory subdir)
 
{
 
	char buf[MAX_PATH];
 
	strecpy(buf, file, lastof(buf));
 
	strecat(buf, ".title", lastof(buf));
 
	std::string buf = file;
 
	buf += ".title";
 

	
 
	FILE *f = FioFOpenFile(buf, "r", subdir);
 
	if (f == nullptr) return;
 

	
 
	size_t read = fread(title, 1, last - title, f);
 
	assert(title + read <= last);
 
	title[read] = '\0';
 
	str_validate(title, last);
 
	FioFCloseFile(f);
 
}
 

	
 
/**
 
 * Callback for FiosGetFileList. It tells if a file is a savegame or not.
 
 * @param fop Purpose of collecting the list.
 
 * @param file Name of the file to check.
 
 * @param ext A pointer to the extension identifier inside file
 
 * @param title Buffer if a callback wants to lookup the title of the file; nullptr to skip the lookup
 
 * @param last Last available byte in buffer (to prevent buffer overflows); not used when title == nullptr
 
 * @return a FIOS_TYPE_* type of the found file, FIOS_TYPE_INVALID if not a savegame
 
 * @see FiosGetFileList
 
 * @see FiosGetSavegameList
 
 */
 
FiosType FiosGetSavegameListCallback(SaveLoadOperation fop, const char *file, const char *ext, char *title, const char *last)
 
FiosType FiosGetSavegameListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last)
 
{
 
	/* Show savegame files
 
	 * .SAV OpenTTD saved game
 
	 * .SS1 Transport Tycoon Deluxe preset game
 
	 * .SV1 Transport Tycoon Deluxe (Patch) saved game
 
	 * .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
 

	
 
	/* Don't crash if we supply no extension */
 
	if (ext == nullptr) return FIOS_TYPE_INVALID;
 

	
 
	if (strcasecmp(ext, ".sav") == 0) {
 
		GetFileTitle(file, title, last, SAVE_DIR);
 
		return FIOS_TYPE_FILE;
 
	}
 

	
 
	if (fop == SLO_LOAD) {
 
		if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
 
				strcasecmp(ext, ".sv2") == 0) {
 
			if (title != nullptr) GetOldSaveGameName(file, title, last);
 
			return FIOS_TYPE_OLDFILE;
 
		}
 
	}
 

	
 
	return FIOS_TYPE_INVALID;
 
@@ -494,90 +494,90 @@ FiosType FiosGetSavegameListCallback(Sav
 
 * @see FiosGetFileList
 
 */
 
void FiosGetSavegameList(SaveLoadOperation fop, FileList &file_list)
 
{
 
	static std::optional<std::string> fios_save_path;
 

	
 
	if (!fios_save_path) fios_save_path = FioFindDirectory(SAVE_DIR);
 

	
 
	_fios_path = &(*fios_save_path);
 

	
 
	FiosGetFileList(fop, &FiosGetSavegameListCallback, NO_DIRECTORY, file_list);
 
}
 

	
 
/**
 
 * Callback for FiosGetFileList. It tells if a file is a scenario or not.
 
 * @param fop Purpose of collecting the list.
 
 * @param file Name of the file to check.
 
 * @param ext A pointer to the extension identifier inside file
 
 * @param title Buffer if a callback wants to lookup the title of the file
 
 * @param last Last available byte in buffer (to prevent buffer overflows)
 
 * @return a FIOS_TYPE_* type of the found file, FIOS_TYPE_INVALID if not a scenario
 
 * @see FiosGetFileList
 
 * @see FiosGetScenarioList
 
 */
 
static FiosType FiosGetScenarioListCallback(SaveLoadOperation fop, const char *file, const char *ext, char *title, const char *last)
 
static FiosType FiosGetScenarioListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last)
 
{
 
	/* Show scenario files
 
	 * .SCN OpenTTD style scenario file
 
	 * .SV0 Transport Tycoon Deluxe (Patch) scenario
 
	 * .SS0 Transport Tycoon Deluxe preset scenario */
 
	if (strcasecmp(ext, ".scn") == 0) {
 
		GetFileTitle(file, title, last, SCENARIO_DIR);
 
		return FIOS_TYPE_SCENARIO;
 
	}
 

	
 
	if (fop == SLO_LOAD) {
 
		if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
 
			GetOldSaveGameName(file, title, last);
 
			return FIOS_TYPE_OLD_SCENARIO;
 
		}
 
	}
 

	
 
	return FIOS_TYPE_INVALID;
 
}
 

	
 
/**
 
 * Get a list of scenarios.
 
 * @param fop Purpose of collecting the list.
 
 * @param file_list Destination of the found files.
 
 * @see FiosGetFileList
 
 */
 
void FiosGetScenarioList(SaveLoadOperation fop, FileList &file_list)
 
{
 
	static std::optional<std::string> fios_scn_path;
 

	
 
	/* Copy the default path on first run or on 'New Game' */
 
	if (!fios_scn_path) fios_scn_path = FioFindDirectory(SCENARIO_DIR);
 

	
 
	_fios_path = &(*fios_scn_path);
 

	
 
	std::string base_path = FioFindDirectory(SCENARIO_DIR);
 
	Subdirectory subdir = (fop == SLO_LOAD && base_path == *_fios_path) ? SCENARIO_DIR : NO_DIRECTORY;
 
	FiosGetFileList(fop, &FiosGetScenarioListCallback, subdir, file_list);
 
}
 

	
 
static FiosType FiosGetHeightmapListCallback(SaveLoadOperation fop, const char *file, const char *ext, char *title, const char *last)
 
static FiosType FiosGetHeightmapListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last)
 
{
 
	/* Show heightmap files
 
	 * .PNG PNG Based heightmap files
 
	 * .BMP BMP Based heightmap files
 
	 */
 

	
 
	FiosType type = FIOS_TYPE_INVALID;
 

	
 
#ifdef WITH_PNG
 
	if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
 
#endif /* WITH_PNG */
 

	
 
	if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
 

	
 
	if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
 

	
 
	TarFileList::iterator it = _tar_filelist[SCENARIO_DIR].find(file);
 
	if (it != _tar_filelist[SCENARIO_DIR].end()) {
 
		/* If the file is in a tar and that tar is not in a heightmap
 
		 * directory we are for sure not supposed to see it.
 
		 * Examples of this are pngs part of documentation within
 
		 * collections of NewGRFs or 32 bpp graphics replacement PNGs.
 
		 */
 
		bool match = false;
 
@@ -648,70 +648,67 @@ struct ScenarioIdentifier {
 
	}
 
};
 

	
 
/**
 
 * Scanner to find the unique IDs of scenarios
 
 */
 
class ScenarioScanner : protected FileScanner, public std::vector<ScenarioIdentifier> {
 
	bool scanned; ///< Whether we've already scanned
 
public:
 
	/** Initialise */
 
	ScenarioScanner() : scanned(false) {}
 

	
 
	/**
 
	 * Scan, but only if it's needed.
 
	 * @param rescan whether to force scanning even when it's not necessary
 
	 */
 
	void Scan(bool rescan)
 
	{
 
		if (this->scanned && !rescan) return;
 

	
 
		this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
 
		this->scanned = true;
 
	}
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
 
	{
 
		FILE *f = FioFOpenFile(filename, "r", SCENARIO_DIR);
 
		if (f == nullptr) return false;
 

	
 
		ScenarioIdentifier id;
 
		int fret = fscanf(f, "%u", &id.scenid);
 
		FioFCloseFile(f);
 
		if (fret != 1) return false;
 
		strecpy(id.filename, filename, lastof(id.filename));
 
		strecpy(id.filename, filename.c_str(), lastof(id.filename));
 

	
 
		Md5 checksum;
 
		uint8 buffer[1024];
 
		char basename[MAX_PATH]; ///< \a filename without the extension.
 
		size_t len, size;
 

	
 
		/* open the scenario file, but first get the name.
 
		 * This is safe as we check on extension which
 
		 * must always exist. */
 
		strecpy(basename, filename, lastof(basename));
 
		*strrchr(basename, '.') = '\0';
 
		f = FioFOpenFile(basename, "rb", SCENARIO_DIR, &size);
 
		f = FioFOpenFile(filename.substr(0, filename.rfind('.')), "rb", SCENARIO_DIR, &size);
 
		if (f == nullptr) return false;
 

	
 
		/* calculate md5sum */
 
		while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
 
			size -= len;
 
			checksum.Append(buffer, len);
 
		}
 
		checksum.Finish(id.md5sum);
 

	
 
		FioFCloseFile(f);
 

	
 
		include(*this, id);
 
		return true;
 
	}
 
};
 

	
 
/** Scanner for scenarios */
 
static ScenarioScanner _scanner;
 

	
 
/**
 
 * Find a given scenario based on its unique ID.
 
 * @param ci The content info to compare it to.
 
 * @param md5sum Whether to look at the md5sum or the id.
 
 * @return The filename of the file, else \c nullptr.
src/fios.h
Show inline comments
 
@@ -202,27 +202,27 @@ public:
 
enum SortingBits {
 
	SORT_ASCENDING  = 0,
 
	SORT_DESCENDING = 1,
 
	SORT_BY_DATE    = 0,
 
	SORT_BY_NAME    = 2
 
};
 
DECLARE_ENUM_AS_BIT_SET(SortingBits)
 

	
 
/* Variables to display file lists */
 
extern SortingBits _savegame_sort_order;
 

	
 
void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop);
 

	
 
void FiosGetSavegameList(SaveLoadOperation fop, FileList &file_list);
 
void FiosGetScenarioList(SaveLoadOperation fop, FileList &file_list);
 
void FiosGetHeightmapList(SaveLoadOperation fop, FileList &file_list);
 

	
 
const char *FiosBrowseTo(const FiosItem *item);
 

	
 
StringID FiosGetDescText(const char **path, uint64 *total_free);
 
bool FiosDelete(const char *name);
 
std::string FiosMakeHeightmapName(const char *name);
 
std::string FiosMakeSavegameName(const char *name);
 

	
 
FiosType FiosGetSavegameListCallback(SaveLoadOperation fop, const char *file, const char *ext, char *title, const char *last);
 
FiosType FiosGetSavegameListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last);
 

	
 
#endif /* FIOS_H */
src/game/game_text.cpp
Show inline comments
 
@@ -185,101 +185,101 @@ struct StringNameWriter : HeaderWriter {
 
		/* Nothing to do. */
 
	}
 
};
 

	
 
/**
 
 * Scanner to find language files in a GameScript directory.
 
 */
 
class LanguageScanner : protected FileScanner {
 
private:
 
	GameStrings *gs;
 
	std::string exclude;
 

	
 
public:
 
	/** Initialise */
 
	LanguageScanner(GameStrings *gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
 

	
 
	/**
 
	 * Scan.
 
	 */
 
	void Scan(const char *directory)
 
	{
 
		this->FileScanner::Scan(".txt", directory, false);
 
	}
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
 
	{
 
		if (exclude == filename) return true;
 

	
 
		auto ls = ReadRawLanguageStrings(filename);
 
		if (!ls.IsValid()) return false;
 

	
 
		gs->raw_strings.push_back(std::move(ls));
 
		return true;
 
	}
 
};
 

	
 
/**
 
 * Load all translations that we know of.
 
 * @return Container with all (compiled) translations.
 
 */
 
GameStrings *LoadTranslations()
 
{
 
	const GameInfo *info = Game::GetInfo();
 
	std::string basename(info->GetMainScript());
 
	auto e = basename.rfind(PATHSEPCHAR);
 
	if (e == std::string::npos) return nullptr;
 
	basename.erase(e + 1);
 

	
 
	std::string filename = basename + "lang" PATHSEP "english.txt";
 
	if (!FioCheckFileExists(filename.c_str() , GAME_DIR)) return nullptr;
 

	
 
	auto ls = ReadRawLanguageStrings(filename);
 
	if (!ls.IsValid()) return nullptr;
 

	
 
	GameStrings *gs = new GameStrings();
 
	try {
 
		gs->raw_strings.push_back(std::move(ls));
 

	
 
		/* Scan for other language files */
 
		LanguageScanner scanner(gs, filename);
 
		std::string ldir = basename + "lang" PATHSEP;
 

	
 
		const char *tar_filename = info->GetTarFile();
 
		const std::string tar_filename = info->GetTarFile();
 
		TarList::iterator iter;
 
		if (tar_filename != nullptr && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
 
		if (!tar_filename.empty() && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
 
			/* The main script is in a tar file, so find all files that
 
			 * are in the same tar and add them to the langfile scanner. */
 
			TarFileList::iterator tar;
 
			FOR_ALL_TARS(tar, GAME_DIR) {
 
				/* Not in the same tar. */
 
				if (tar->second.tar_filename != iter->first) continue;
 

	
 
				/* Check the path and extension. */
 
				if (tar->first.size() <= ldir.size() || tar->first.compare(0, ldir.size(), ldir) != 0) continue;
 
				if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
 

	
 
				scanner.AddFile(tar->first.c_str(), 0, tar_filename);
 
				scanner.AddFile(tar->first, 0, tar_filename);
 
			}
 
		} else {
 
			/* Scan filesystem */
 
			scanner.Scan(ldir.c_str());
 
		}
 

	
 
		gs->Compile();
 
		return gs;
 
	} catch (...) {
 
		delete gs;
 
		return nullptr;
 
	}
 
}
 

	
 
/** Compile the language. */
 
void GameStrings::Compile()
 
{
 
	StringData data(32);
 
	StringListReader master_reader(data, this->raw_strings[0], true, false);
 
	master_reader.ParseFile();
 
	if (_errors != 0) throw std::exception();
 

	
 
	this->version = data.Version();
 

	
src/newgrf_config.cpp
Show inline comments
 
@@ -565,65 +565,65 @@ compatible_grf:
 
				for (uint i = 0; i < f->param_info.size(); i++) {
 
					if (f->param_info[i] == nullptr) {
 
						c->param_info.push_back(nullptr);
 
					} else {
 
						c->param_info.push_back(new GRFParameterInfo(*f->param_info[i]));
 
					}
 
				}
 
			}
 
		}
 
	}
 

	
 
	return res;
 
}
 

	
 
/** Helper for scanning for files with GRF as extension */
 
class GRFFileScanner : FileScanner {
 
	uint next_update; ///< The next (realtime tick) we do update the screen.
 
	uint num_scanned; ///< The number of GRFs we have scanned.
 

	
 
public:
 
	GRFFileScanner() : next_update(_realtime_tick), num_scanned(0)
 
	{
 
	}
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override;
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override;
 

	
 
	/** Do the scan for GRFs. */
 
	static uint DoScan()
 
	{
 
		GRFFileScanner fs;
 
		int ret = fs.Scan(".grf", NEWGRF_DIR);
 
		/* The number scanned and the number returned may not be the same;
 
		 * duplicate NewGRFs and base sets are ignored in the return value. */
 
		_settings_client.gui.last_newgrf_count = fs.num_scanned;
 
		return ret;
 
	}
 
};
 

	
 
bool GRFFileScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
bool GRFFileScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
{
 
	GRFConfig *c = new GRFConfig(filename + basepath_length);
 
	GRFConfig *c = new GRFConfig(filename.c_str() + basepath_length);
 

	
 
	bool added = true;
 
	if (FillGRFDetails(c, false)) {
 
		if (_all_grfs == nullptr) {
 
			_all_grfs = c;
 
		} else {
 
			/* Insert file into list at a position determined by its
 
			 * name, so the list is sorted as we go along */
 
			GRFConfig **pd, *d;
 
			bool stop = false;
 
			for (pd = &_all_grfs; (d = *pd) != nullptr; pd = &d->next) {
 
				if (c->ident.grfid == d->ident.grfid && memcmp(c->ident.md5sum, d->ident.md5sum, sizeof(c->ident.md5sum)) == 0) added = false;
 
				/* Because there can be multiple grfs with the same name, make sure we checked all grfs with the same name,
 
				 *  before inserting the entry. So insert a new grf at the end of all grfs with the same name, instead of
 
				 *  just after the first with the same name. Avoids doubles in the list. */
 
				if (strcasecmp(c->GetName(), d->GetName()) <= 0) {
 
					stop = true;
 
				} else if (stop) {
 
					break;
 
				}
 
			}
 
			if (added) {
 
				c->next = d;
 
				*pd = c;
src/openttd.cpp
Show inline comments
 
@@ -607,49 +607,49 @@ int openttd_main(int argc, char *argv[])
 
			break;
 
		case 'P':
 
			scanner->join_company_password = mgo.opt;
 
			break;
 
		case 'r': ParseResolution(&resolution, mgo.opt); break;
 
		case 't': scanner->startyear = atoi(mgo.opt); break;
 
		case 'd': {
 
#if defined(_WIN32)
 
				CreateConsole();
 
#endif
 
				if (mgo.opt != nullptr) SetDebugString(mgo.opt);
 
				break;
 
			}
 
		case 'e': _switch_mode = (_switch_mode == SM_LOAD_GAME || _switch_mode == SM_LOAD_SCENARIO ? SM_LOAD_SCENARIO : SM_EDITOR); break;
 
		case 'g':
 
			if (mgo.opt != nullptr) {
 
				_file_to_saveload.SetName(mgo.opt);
 
				bool is_scenario = _switch_mode == SM_EDITOR || _switch_mode == SM_LOAD_SCENARIO;
 
				_switch_mode = is_scenario ? SM_LOAD_SCENARIO : SM_LOAD_GAME;
 
				_file_to_saveload.SetMode(SLO_LOAD, is_scenario ? FT_SCENARIO : FT_SAVEGAME, DFT_GAME_FILE);
 

	
 
				/* if the file doesn't exist or it is not a valid savegame, let the saveload code show an error */
 
				auto t = _file_to_saveload.name.find_last_of('.');
 
				if (t != std::string::npos) {
 
					FiosType ft = FiosGetSavegameListCallback(SLO_LOAD, _file_to_saveload.name.c_str(), _file_to_saveload.name.substr(t).c_str(), nullptr, nullptr);
 
					FiosType ft = FiosGetSavegameListCallback(SLO_LOAD, _file_to_saveload.name, _file_to_saveload.name.substr(t).c_str(), nullptr, nullptr);
 
					if (ft != FIOS_TYPE_INVALID) _file_to_saveload.SetMode(ft);
 
				}
 

	
 
				break;
 
			}
 

	
 
			_switch_mode = SM_NEWGAME;
 
			/* Give a random map if no seed has been given */
 
			if (scanner->generation_seed == GENERATE_NEW_SEED) {
 
				scanner->generation_seed = InteractiveRandom();
 
			}
 
			break;
 
		case 'q': {
 
			DeterminePaths(argv[0]);
 
			if (StrEmpty(mgo.opt)) {
 
				ret = 1;
 
				return ret;
 
			}
 

	
 
			char title[80];
 
			title[0] = '\0';
 
			FiosGetSavegameListCallback(SLO_LOAD, mgo.opt, strrchr(mgo.opt, '.'), title, lastof(title));
 

	
 
			_load_check_data.Clear();
src/saveload/oldloader.cpp
Show inline comments
 
@@ -296,37 +296,37 @@ bool LoadOldSaveGame(const std::string &
 
		case SGT_TTD: proc = &LoadTTDMain; break;
 
		default: break;
 
	}
 

	
 
	_savegame_type = type;
 

	
 
	bool game_loaded;
 
	try {
 
		game_loaded = proc != nullptr && proc(&ls);
 
	} catch (...) {
 
		game_loaded = false;
 
	}
 

	
 
	if (!game_loaded) {
 
		SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
 
		fclose(ls.file);
 
		return false;
 
	}
 

	
 
	_pause_mode = PM_PAUSED_SAVELOAD;
 

	
 
	return true;
 
}
 

	
 
void GetOldSaveGameName(const char *file, char *title, const char *last)
 
void GetOldSaveGameName(const std::string &file, char *title, const char *last)
 
{
 
	FILE *f = FioFOpenFile(file, "rb", NO_DIRECTORY);
 

	
 
	if (f == nullptr) {
 
		*title = '\0';
 
		return;
 
	}
 

	
 
	DetermineOldSavegameType(f, title, last);
 

	
 
	fclose(f);
 
}
src/script/script_info.cpp
Show inline comments
 
@@ -18,93 +18,90 @@
 
#include "../safeguards.h"
 

	
 
ScriptInfo::~ScriptInfo()
 
{
 
	/* Free all allocated strings */
 
	for (const auto &item : this->config_list) {
 
		free(item.name);
 
		free(item.description);
 
		if (item.labels != nullptr) {
 
			for (auto &lbl_map : *item.labels) {
 
				free(lbl_map.second);
 
			}
 
			delete item.labels;
 
		}
 
	}
 
	this->config_list.clear();
 

	
 
	free(this->author);
 
	free(this->name);
 
	free(this->short_name);
 
	free(this->description);
 
	free(this->date);
 
	free(this->instance_name);
 
	free(this->url);
 
	free(this->main_script);
 
	free(this->tar_file);
 
	free(this->SQ_instance);
 
}
 

	
 
bool ScriptInfo::CheckMethod(const char *name) const
 
{
 
	if (!this->engine->MethodExists(*this->SQ_instance, name)) {
 
		char error[1024];
 
		seprintf(error, lastof(error), "your info.nut/library.nut doesn't have the method '%s'", name);
 
		this->engine->ThrowError(error);
 
		return false;
 
	}
 
	return true;
 
}
 

	
 
/* static */ SQInteger ScriptInfo::Constructor(HSQUIRRELVM vm, ScriptInfo *info)
 
{
 
	/* Set some basic info from the parent */
 
	info->SQ_instance = MallocT<SQObject>(1);
 
	Squirrel::GetInstance(vm, info->SQ_instance, 2);
 
	/* Make sure the instance stays alive over time */
 
	sq_addref(vm, info->SQ_instance);
 

	
 
	info->scanner = (ScriptScanner *)Squirrel::GetGlobalPointer(vm);
 
	info->engine = info->scanner->GetEngine();
 

	
 
	/* Ensure the mandatory functions exist */
 
	static const char * const required_functions[] = {
 
		"GetAuthor",
 
		"GetName",
 
		"GetShortName",
 
		"GetDescription",
 
		"GetVersion",
 
		"GetDate",
 
		"CreateInstance",
 
	};
 
	for (size_t i = 0; i < lengthof(required_functions); i++) {
 
		if (!info->CheckMethod(required_functions[i])) return SQ_ERROR;
 
	}
 

	
 
	/* Get location information of the scanner */
 
	info->main_script = stredup(info->scanner->GetMainScript());
 
	const char *tar_name = info->scanner->GetTarFile();
 
	if (tar_name != nullptr) info->tar_file = stredup(tar_name);
 
	info->main_script = info->scanner->GetMainScript();
 
	info->tar_file = info->scanner->GetTarFile();
 

	
 
	/* Cache the data the info file gives us. */
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR;
 
	if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR;
 

	
 
	/* The GetURL function is optional. */
 
	if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) {
 
		if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR;
 
	}
 

	
 
	/* Check if we have settings */
 
	if (info->engine->MethodExists(*info->SQ_instance, "GetSettings")) {
 
		if (!info->GetSettings()) return SQ_ERROR;
 
	}
 

	
 
	return 0;
 
}
 

	
 
bool ScriptInfo::GetSettings()
src/script/script_info.hpp
Show inline comments
 
@@ -11,50 +11,48 @@
 
#define SCRIPT_INFO_HPP
 

	
 
#include <squirrel.h>
 
#include "../misc/countedptr.hpp"
 

	
 
#include "script_config.hpp"
 

	
 
/** The maximum number of operations for saving or loading the data of a script. */
 
static const int MAX_SL_OPS             = 100000;
 
/** The maximum number of operations for initial start of a script. */
 
static const int MAX_CONSTRUCTOR_OPS    = 100000;
 
/** Number of operations to create an instance of a script. */
 
static const int MAX_CREATEINSTANCE_OPS = 100000;
 
/** Number of operations to get the author and similar information. */
 
static const int MAX_GET_OPS            =   1000;
 
/** Maximum number of operations allowed for getting a particular setting. */
 
static const int MAX_GET_SETTING_OPS    = 100000;
 

	
 
/** All static information from an Script like name, version, etc. */
 
class ScriptInfo : public SimpleCountedObject {
 
public:
 
	ScriptInfo() :
 
		engine(nullptr),
 
		SQ_instance(nullptr),
 
		main_script(nullptr),
 
		tar_file(nullptr),
 
		author(nullptr),
 
		name(nullptr),
 
		short_name(nullptr),
 
		description(nullptr),
 
		date(nullptr),
 
		instance_name(nullptr),
 
		version(0),
 
		url(nullptr),
 
		scanner(nullptr)
 
	{}
 
	~ScriptInfo();
 

	
 
	/**
 
	 * Get the Author of the script.
 
	 */
 
	const char *GetAuthor() const { return this->author; }
 

	
 
	/**
 
	 * Get the Name of the script.
 
	 */
 
	const char *GetName() const { return this->name; }
 

	
 
	/**
 
	 * Get the 4 character long short name of the script.
 
@@ -68,54 +66,54 @@ public:
 

	
 
	/**
 
	 * Get the version of the script.
 
	 */
 
	int GetVersion() const { return this->version; }
 

	
 
	/**
 
	 * Get the last-modified date of the script.
 
	 */
 
	const char *GetDate() const { return this->date; }
 

	
 
	/**
 
	 * Get the name of the instance of the script to create.
 
	 */
 
	const char *GetInstanceName() const { return this->instance_name; }
 

	
 
	/**
 
	 * Get the website for this script.
 
	 */
 
	const char *GetURL() const { return this->url; }
 

	
 
	/**
 
	 * Get the filename of the main.nut script.
 
	 */
 
	const char *GetMainScript() const { return this->main_script; }
 
	const char *GetMainScript() const { return this->main_script.c_str(); }
 

	
 
	/**
 
	 * Get the filename of the tar the script is in.
 
	 */
 
	const char *GetTarFile() const { return this->tar_file; }
 
	std::string GetTarFile() const { return this->tar_file; }
 

	
 
	/**
 
	 * Check if a given method exists.
 
	 */
 
	bool CheckMethod(const char *name) const;
 

	
 
	/**
 
	 * Process the creation of a FileInfo object.
 
	 */
 
	static SQInteger Constructor(HSQUIRRELVM vm, ScriptInfo *info);
 

	
 
	/**
 
	 * Get the scanner which has found this ScriptInfo.
 
	 */
 
	virtual class ScriptScanner *GetScanner() { return this->scanner; }
 

	
 
	/**
 
	 * Get the settings of the Script.
 
	 */
 
	bool GetSettings();
 

	
 
	/**
 
	 * Get the config list for this Script.
 
	 */
 
@@ -131,39 +129,39 @@ public:
 
	 */
 
	SQInteger AddSetting(HSQUIRRELVM vm);
 

	
 
	/**
 
	 * Add labels for a setting.
 
	 */
 
	SQInteger AddLabels(HSQUIRRELVM vm);
 

	
 
	/**
 
	 * Get the default value for a setting.
 
	 */
 
	int GetSettingDefaultValue(const char *name) const;
 

	
 
	/**
 
	 * Can this script be selected by developers only?
 
	 */
 
	virtual bool IsDeveloperOnly() const { return false; }
 

	
 
protected:
 
	class Squirrel *engine;           ///< Engine used to register for Squirrel.
 
	HSQOBJECT *SQ_instance;           ///< The Squirrel instance created for this info.
 
	ScriptConfigItemList config_list; ///< List of settings from this Script.
 

	
 
private:
 
	char *main_script;            ///< The full path of the script.
 
	char *tar_file;               ///< If, which tar file the script was in.
 
	std::string main_script;      ///< The full path of the script.
 
	std::string tar_file;         ///< If, which tar file the script was in.
 
	const char *author;           ///< Author of the script.
 
	const char *name;             ///< Full name of the script.
 
	const char *short_name;       ///< Short name (4 chars) which uniquely identifies the script.
 
	const char *description;      ///< Small description of the script.
 
	const char *date;             ///< The date the script was written at.
 
	const char *instance_name;    ///< Name of the main class in the script.
 
	int version;                  ///< Version of the script.
 
	const char *url;              ///< URL of the script.
 

	
 
	class ScriptScanner *scanner; ///< ScriptScanner object that was used to scan this script info.
 
};
 

	
 
#endif /* SCRIPT_INFO_HPP */
src/script/script_scanner.cpp
Show inline comments
 
@@ -2,114 +2,94 @@
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file script_scanner.cpp Allows scanning for scripts. */
 

	
 
#include "../stdafx.h"
 
#include "../debug.h"
 
#include "../string_func.h"
 
#include "../settings_type.h"
 

	
 
#include "../script/squirrel.hpp"
 
#include "script_scanner.hpp"
 
#include "script_info.hpp"
 
#include "script_fatalerror.hpp"
 

	
 
#include "../network/network_content.h"
 
#include "../3rdparty/md5/md5.h"
 
#include "../tar_type.h"
 

	
 
#include "../safeguards.h"
 

	
 
bool ScriptScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
bool ScriptScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
{
 
	free(this->main_script);
 
	this->main_script = stredup(filename);
 
	if (this->main_script == nullptr) return false;
 
	this->main_script = filename;
 
	this->tar_file = tar_filename;
 

	
 
	free(this->tar_file);
 
	if (tar_filename != nullptr) {
 
		this->tar_file = stredup(tar_filename);
 
		if (this->tar_file == nullptr) return false;
 
	} else {
 
		this->tar_file = nullptr;
 
	}
 

	
 
	const char *end = this->main_script + strlen(this->main_script) + 1;
 
	char *p = strrchr(this->main_script, PATHSEPCHAR);
 
	if (p == nullptr) {
 
		p = this->main_script;
 
	} else {
 
		/* Skip over the path separator character. We don't need that. */
 
		p++;
 
	}
 

	
 
	strecpy(p, "main.nut", end);
 
	auto p = this->main_script.rfind(PATHSEPCHAR);
 
	this->main_script.erase(p != std::string::npos ? p + 1 : 0);
 
	this->main_script += "main.nut";
 

	
 
	if (!FioCheckFileExists(filename, this->subdir) || !FioCheckFileExists(this->main_script, this->subdir)) return false;
 

	
 
	this->ResetEngine();
 
	try {
 
		this->engine->LoadScript(filename);
 
		this->engine->LoadScript(filename.c_str());
 
	} catch (Script_FatalError &e) {
 
		DEBUG(script, 0, "Fatal error '%s' when trying to load the script '%s'.", e.GetErrorMessage(), filename);
 
		DEBUG(script, 0, "Fatal error '%s' when trying to load the script '%s'.", e.GetErrorMessage(), filename.c_str());
 
		return false;
 
	}
 
	return true;
 
}
 

	
 
ScriptScanner::ScriptScanner() :
 
	engine(nullptr),
 
	main_script(nullptr),
 
	tar_file(nullptr)
 
	engine(nullptr)
 
{
 
}
 

	
 
void ScriptScanner::ResetEngine()
 
{
 
	this->engine->Reset();
 
	this->engine->SetGlobalPointer(this);
 
	this->RegisterAPI(this->engine);
 
}
 

	
 
void ScriptScanner::Initialize(const char *name)
 
{
 
	this->engine = new Squirrel(name);
 

	
 
	this->RescanDir();
 

	
 
	this->ResetEngine();
 
}
 

	
 
ScriptScanner::~ScriptScanner()
 
{
 
	this->Reset();
 

	
 
	free(this->main_script);
 
	free(this->tar_file);
 
	delete this->engine;
 
}
 

	
 
void ScriptScanner::RescanDir()
 
{
 
	/* Forget about older scans */
 
	this->Reset();
 

	
 
	/* Scan for scripts */
 
	this->Scan(this->GetFileName(), this->GetDirectory());
 
}
 

	
 
void ScriptScanner::Reset()
 
{
 
	for (const auto &item : this->info_list) {
 
		free(item.first);
 
		delete item.second;
 
	}
 
	for (const auto &item : this->info_single_list) {
 
		free(item.first);
 
	}
 

	
 
	this->info_list.clear();
 
	this->info_single_list.clear();
 
@@ -173,108 +153,108 @@ char *ScriptScanner::GetConsoleList(char
 
		ScriptInfo *i = item.second;
 
		p += seprintf(p, last, "%10s (v%d): %s\n", i->GetName(), i->GetVersion(), i->GetDescription());
 
	}
 
	p += seprintf(p, last, "\n");
 

	
 
	return p;
 
}
 

	
 
/** Helper for creating a MD5sum of all files within of a script. */
 
struct ScriptFileChecksumCreator : FileScanner {
 
	byte md5sum[16];  ///< The final md5sum.
 
	Subdirectory dir; ///< The directory to look in.
 

	
 
	/**
 
	 * Initialise the md5sum to be all zeroes,
 
	 * so we can easily xor the data.
 
	 */
 
	ScriptFileChecksumCreator(Subdirectory dir)
 
	{
 
		this->dir = dir;
 
		memset(this->md5sum, 0, sizeof(this->md5sum));
 
	}
 

	
 
	/* Add the file and calculate the md5 sum. */
 
	virtual bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
 
	virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
 
	{
 
		Md5 checksum;
 
		uint8 buffer[1024];
 
		size_t len, size;
 
		byte tmp_md5sum[16];
 

	
 
		/* Open the file ... */
 
		FILE *f = FioFOpenFile(filename, "rb", this->dir, &size);
 
		FILE *f = FioFOpenFile(filename.c_str(), "rb", this->dir, &size);
 
		if (f == nullptr) return false;
 

	
 
		/* ... calculate md5sum... */
 
		while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
 
			size -= len;
 
			checksum.Append(buffer, len);
 
		}
 
		checksum.Finish(tmp_md5sum);
 

	
 
		FioFCloseFile(f);
 

	
 
		/* ... and xor it to the overall md5sum. */
 
		for (uint i = 0; i < sizeof(md5sum); i++) this->md5sum[i] ^= tmp_md5sum[i];
 

	
 
		return true;
 
	}
 
};
 

	
 
/**
 
 * Check whether the script given in info is the same as in ci based
 
 * on the shortname and md5 sum.
 
 * @param ci The information to compare to.
 
 * @param md5sum Whether to check the MD5 checksum.
 
 * @param info The script to get the shortname and md5 sum from.
 
 * @return True iff they're the same.
 
 */
 
static bool IsSameScript(const ContentInfo *ci, bool md5sum, ScriptInfo *info, Subdirectory dir)
 
{
 
	uint32 id = 0;
 
	const char *str = info->GetShortName();
 
	for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
 

	
 
	if (id != ci->unique_id) return false;
 
	if (!md5sum) return true;
 

	
 
	ScriptFileChecksumCreator checksum(dir);
 
	const char *tar_filename = info->GetTarFile();
 
	auto tar_filename = info->GetTarFile();
 
	TarList::iterator iter;
 
	if (tar_filename != nullptr && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) {
 
	if (!tar_filename.empty() && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) {
 
		/* The main script is in a tar file, so find all files that
 
		 * are in the same tar and add them to the MD5 checksumming. */
 
		TarFileList::iterator tar;
 
		FOR_ALL_TARS(tar, dir) {
 
			/* Not in the same tar. */
 
			if (tar->second.tar_filename != iter->first) continue;
 

	
 
			/* Check the extension. */
 
			const char *ext = strrchr(tar->first.c_str(), '.');
 
			if (ext == nullptr || strcasecmp(ext, ".nut") != 0) continue;
 

	
 
			checksum.AddFile(tar->first.c_str(), 0, tar_filename);
 
			checksum.AddFile(tar->first, 0, tar_filename);
 
		}
 
	} else {
 
		char path[MAX_PATH];
 
		strecpy(path, info->GetMainScript(), lastof(path));
 
		/* There'll always be at least 1 path separator character in a script
 
		 * main script name as the search algorithm requires the main script to
 
		 * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
 
		*strrchr(path, PATHSEPCHAR) = '\0';
 
		checksum.Scan(".nut", path);
 
	}
 

	
 
	return memcmp(ci->md5sum, checksum.md5sum, sizeof(ci->md5sum)) == 0;
 
}
 

	
 
bool ScriptScanner::HasScript(const ContentInfo *ci, bool md5sum)
 
{
 
	for (const auto &item : this->info_list) {
 
		if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return true;
 
	}
 
	return false;
 
}
 

	
 
const char *ScriptScanner::FindMainScript(const ContentInfo *ci, bool md5sum)
 
{
src/script/script_scanner.hpp
Show inline comments
 
@@ -11,102 +11,102 @@
 
#define SCRIPT_SCANNER_HPP
 

	
 
#include <map>
 
#include "../fileio_func.h"
 
#include "../core/string_compare_type.hpp"
 

	
 
typedef std::map<const char *, class ScriptInfo *, StringCompare> ScriptInfoList; ///< Type for the list of scripts.
 

	
 
/** Scanner to help finding scripts. */
 
class ScriptScanner : public FileScanner {
 
public:
 
	ScriptScanner();
 
	virtual ~ScriptScanner();
 

	
 
	virtual void Initialize() = 0;
 

	
 
	/**
 
	 * Get the engine of the main squirrel handler (it indexes all available scripts).
 
	 */
 
	class Squirrel *GetEngine() { return this->engine; }
 

	
 
	/**
 
	 * Get the current main script the ScanDir is currently tracking.
 
	 */
 
	const char *GetMainScript() { return this->main_script; }
 
	std::string GetMainScript() { return this->main_script; }
 

	
 
	/**
 
	 * Get the current tar file the ScanDir is currently tracking.
 
	 */
 
	const char *GetTarFile() { return this->tar_file; }
 
	std::string GetTarFile() { return this->tar_file; }
 

	
 
	/**
 
	 * Get the list of all registered scripts.
 
	 */
 
	const ScriptInfoList *GetInfoList() { return &this->info_list; }
 

	
 
	/**
 
	 * Get the list of the latest version of all registered scripts.
 
	 */
 
	const ScriptInfoList *GetUniqueInfoList() { return &this->info_single_list; }
 

	
 
	/**
 
	 * Register a ScriptInfo to the scanner.
 
	 */
 
	void RegisterScript(class ScriptInfo *info);
 

	
 
	/**
 
	 * Get the list of registered scripts to print on the console.
 
	 */
 
	char *GetConsoleList(char *p, const char *last, bool newest_only) const;
 

	
 
	/**
 
	 * Check whether we have a script with the exact characteristics as ci.
 
	 * @param ci The characteristics to search on (shortname and md5sum).
 
	 * @param md5sum Whether to check the MD5 checksum.
 
	 * @return True iff we have a script matching.
 
	 */
 
	bool HasScript(const struct ContentInfo *ci, bool md5sum);
 

	
 
	/**
 
	 * Find a script of a #ContentInfo
 
	 * @param ci The information to compare to.
 
	 * @param md5sum Whether to check the MD5 checksum.
 
	 * @return A filename of a file of the content, else \c nullptr.
 
	 */
 
	const char *FindMainScript(const ContentInfo *ci, bool md5sum);
 

	
 
	bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override;
 
	bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override;
 

	
 
	/**
 
	 * Rescan the script dir.
 
	 */
 
	void RescanDir();
 

	
 
protected:
 
	class Squirrel *engine; ///< The engine we're scanning with.
 
	char *main_script;      ///< The full path of the script.
 
	char *tar_file;         ///< If, which tar file the script was in.
 
	class Squirrel *engine;  ///< The engine we're scanning with.
 
	std::string main_script; ///< The full path of the script.
 
	std::string tar_file;    ///< If, which tar file the script was in.
 

	
 
	ScriptInfoList info_list;        ///< The list of all script.
 
	ScriptInfoList info_single_list; ///< The list of all unique script. The best script (highest version) is shown.
 

	
 
	/**
 
	 * Initialize the scanner.
 
	 * @param name The name of the scanner ("AIScanner", "GSScanner", ..).
 
	 */
 
	void Initialize(const char *name);
 

	
 
	/**
 
	 * Get the script name how to store the script in memory.
 
	 */
 
	virtual void GetScriptName(ScriptInfo *info, char *name, const char *last) = 0;
 

	
 
	/**
 
	 * Get the filename to scan for this type of script.
 
	 */
 
	virtual const char *GetFileName() const = 0;
 

	
 
	/**
 
	 * Get the directory to scan in.
 
	 */
 
	virtual Subdirectory GetDirectory() const = 0;
src/tar_type.h
Show inline comments
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file tar_type.h Structs, typedefs and macros used for TAR file handling. */
 

	
 
#ifndef TAR_TYPE_H
 
#define TAR_TYPE_H
 

	
 
#include <map>
 
#include <string>
 
#include <array>
 

	
 
#include "fileio_type.h"
 

	
 
/** The define of a TarList. */
 
struct TarListEntry {
 
	const char *filename;
 
	const char *dirname;
 

	
 
	/* MSVC goes copying around this struct after initialisation, so it tries
 
	 * to free filename, which isn't set at that moment... but because it
 
	 * initializes the variable with garbage, it's going to segfault. */
 
	TarListEntry() : filename(nullptr), dirname(nullptr) {}
 
	~TarListEntry() { free(this->filename); free(this->dirname); }
 
};
 

	
 
struct TarFileListEntry {
 
	const char *tar_filename;
 
	std::string tar_filename;
 
	size_t size;
 
	size_t position;
 
};
 

	
 
typedef std::map<std::string, TarListEntry> TarList;
 
typedef std::map<std::string, std::string> TarList; ///< Map of tar file to tar directory.
 
typedef std::map<std::string, TarFileListEntry> TarFileList;
 
extern TarList _tar_list[NUM_SUBDIRS];
 
extern std::array<TarList, NUM_SUBDIRS> _tar_list;
 
extern TarFileList _tar_filelist[NUM_SUBDIRS];
 

	
 
#define FOR_ALL_TARS(tar, sd) for (tar = _tar_filelist[sd].begin(); tar != _tar_filelist[sd].end(); tar++)
 

	
 
#endif /* TAR_TYPE_H */
0 comments (0 inline, 0 general)