Files @ r25958:603d75b53498
Branch filter:

Location: cpp/openttd-patchpack/source/src/base_media_func.h - annotation

Patric Stout
Doc: update multiplayer documentation with latest changes (#9552)

Although several places were fixed during the PR making the change,
not all made it in this document.

While at it, removed all kinds of Markdown warnings by an excessive
usage of spacebar in this document.
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r14248:a9050881acd7
r14248:a9050881acd7
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24216:bee2183ce93e
r25655:1030dcb7eb52
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r18368:c399e53439ff
r18368:c399e53439ff
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r13271:4bcd60b30aeb
r13271:4bcd60b30aeb
r23607:36c15679007d
r24216:bee2183ce93e
r13271:4bcd60b30aeb
r24217:df2f5854f1e3
r13271:4bcd60b30aeb
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24433:05e89d8e369b
r24433:05e89d8e369b
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24216:bee2183ce93e
r12668:95f5d46a2c14
r14619:fff04644c054
r24433:05e89d8e369b
r14619:fff04644c054
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r18368:c399e53439ff
r24216:bee2183ce93e
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24216:bee2183ce93e
r23607:36c15679007d
r14055:e4168a875df8
r14055:e4168a875df8
r14055:e4168a875df8
r14055:e4168a875df8
r14055:e4168a875df8
r14055:e4168a875df8
r24216:bee2183ce93e
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24216:bee2183ce93e
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24216:bee2183ce93e
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r20999:02333a2c75c5
r23607:36c15679007d
r24216:bee2183ce93e
r25655:1030dcb7eb52
r21410:ae5961f02724
r12668:95f5d46a2c14
r24216:bee2183ce93e
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r22912:55c82c991a10
r22912:55c82c991a10
r22918:df9cc6173f9b
r22918:df9cc6173f9b
r22918:df9cc6173f9b
r12761:a366bd42c075
r12761:a366bd42c075
r19914:eca70d0d4b75
r19914:eca70d0d4b75
r19914:eca70d0d4b75
r12761:a366bd42c075
r25655:1030dcb7eb52
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r25655:1030dcb7eb52
r12761:a366bd42c075
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24529:3dec691db49a
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r14551:d7dc2b623893
r12668:95f5d46a2c14
r24529:3dec691db49a
r23694:b199bc1e26b1
r12668:95f5d46a2c14
r24522:0c6c6ad8ded0
r24522:0c6c6ad8ded0
r24522:0c6c6ad8ded0
r12668:95f5d46a2c14
r24522:0c6c6ad8ded0
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24529:3dec691db49a
r23607:36c15679007d
r23607:36c15679007d
r24216:bee2183ce93e
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r12668:95f5d46a2c14
r12761:a366bd42c075
r12761:a366bd42c075
r25655:1030dcb7eb52
r19914:eca70d0d4b75
r15913:089431d6de35
r15913:089431d6de35
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12793:9422ba5131c9
r12793:9422ba5131c9
r12793:9422ba5131c9
r12793:9422ba5131c9
r12793:9422ba5131c9
r25655:1030dcb7eb52
r19914:eca70d0d4b75
r15913:089431d6de35
r15913:089431d6de35
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r25655:1030dcb7eb52
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r24217:df2f5854f1e3
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r12761:a366bd42c075
r19944:25a78576fb5e
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r25281:5e55c64244ef
r12668:95f5d46a2c14
r19556:51f34458ed8a
r12668:95f5d46a2c14
r23607:36c15679007d
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r19556:51f34458ed8a
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r19556:51f34458ed8a
r12668:95f5d46a2c14
r23607:36c15679007d
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r15913:089431d6de35
r15913:089431d6de35
r15913:089431d6de35
r23607:36c15679007d
r23607:36c15679007d
r15913:089431d6de35
r15913:089431d6de35
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r12668:95f5d46a2c14
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r23607:36c15679007d
r12761:a366bd42c075
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r17097:4984cc3adcdd
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r19556:51f34458ed8a
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r24529:3dec691db49a
r12668:95f5d46a2c14
r24217:df2f5854f1e3
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r12668:95f5d46a2c14
r19556:51f34458ed8a
r19565:c047d5884fa1
r19565:c047d5884fa1
r12668:95f5d46a2c14
/*
 * 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 base_media_func.h Generic function implementations for base data (graphics, sounds).
 * @note You should _never_ include this file due to the SET_TYPE define.
 */

#include "base_media_base.h"
#include "debug.h"
#include "ini_type.h"
#include "string_func.h"

/**
 * Try to read a single piece of metadata and return false if it doesn't exist.
 * @param name the name of the item to fetch.
 */
#define fetch_metadata(name) \
	item = metadata->GetItem(name, false); \
	if (item == nullptr || !item->value.has_value() || item->value->empty()) { \
		Debug(grf, 0, "Base " SET_TYPE "set detail loading: {} field missing.", name); \
		Debug(grf, 0, "  Is {} readable for the user running OpenTTD?", full_filename); \
		return false; \
	}

/**
 * Read the set information from a loaded ini.
 * @param ini      the ini to read from
 * @param path     the path to this ini file (for filenames)
 * @param full_filename the full filename of the loaded file (for error reporting purposes)
 * @param allow_empty_filename empty filenames are valid
 * @return true if loading was successful.
 */
template <class T, size_t Tnum_files, bool Tsearch_in_tars>
bool BaseSet<T, Tnum_files, Tsearch_in_tars>::FillSetDetails(IniFile *ini, const char *path, const char *full_filename, bool allow_empty_filename)
{
	IniGroup *metadata = ini->GetGroup("metadata");
	IniItem *item;

	fetch_metadata("name");
	this->name = *item->value;

	fetch_metadata("description");
	this->description[std::string{}] = *item->value;

	/* Add the translations of the descriptions too. */
	for (const IniItem *item = metadata->item; item != nullptr; item = item->next) {
		if (item->name.compare(0, 12, "description.") != 0) continue;

		this->description[item->name.substr(12)] = item->value.value_or("");
	}

	fetch_metadata("shortname");
	for (uint i = 0; (*item->value)[i] != '\0' && i < 4; i++) {
		this->shortname |= ((uint8)(*item->value)[i]) << (i * 8);
	}

	fetch_metadata("version");
	this->version = atoi(item->value->c_str());

	item = metadata->GetItem("fallback", false);
	this->fallback = (item != nullptr && item->value && *item->value != "0" && *item->value != "false");

	/* For each of the file types we want to find the file, MD5 checksums and warning messages. */
	IniGroup *files  = ini->GetGroup("files");
	IniGroup *md5s   = ini->GetGroup("md5s");
	IniGroup *origin = ini->GetGroup("origin");
	for (uint i = 0; i < Tnum_files; i++) {
		MD5File *file = &this->files[i];
		/* Find the filename first. */
		item = files->GetItem(BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], false);
		if (item == nullptr || (!item->value.has_value() && !allow_empty_filename)) {
			Debug(grf, 0, "No " SET_TYPE " file for: {} (in {})", BaseSet<T, Tnum_files, Tsearch_in_tars>::file_names[i], full_filename);
			return false;
		}

		if (!item->value.has_value()) {
			file->filename = nullptr;
			/* If we list no file, that file must be valid */
			this->valid_files++;
			this->found_files++;
			continue;
		}

		const char *filename = item->value->c_str();
		file->filename = str_fmt("%s%s", path, filename);

		/* Then find the MD5 checksum */
		item = md5s->GetItem(filename, false);
		if (item == nullptr || !item->value.has_value()) {
			Debug(grf, 0, "No MD5 checksum specified for: {} (in {})", filename, full_filename);
			return false;
		}
		const char *c = item->value->c_str();
		for (uint i = 0; i < sizeof(file->hash) * 2; i++, c++) {
			uint j;
			if ('0' <= *c && *c <= '9') {
				j = *c - '0';
			} else if ('a' <= *c && *c <= 'f') {
				j = *c - 'a' + 10;
			} else if ('A' <= *c && *c <= 'F') {
				j = *c - 'A' + 10;
			} else {
				Debug(grf, 0, "Malformed MD5 checksum specified for: {} (in {})", filename, full_filename);
				return false;
			}
			if (i % 2 == 0) {
				file->hash[i / 2] = j << 4;
			} else {
				file->hash[i / 2] |= j;
			}
		}

		/* Then find the warning message when the file's missing */
		item = origin->GetItem(filename, false);
		if (item == nullptr) item = origin->GetItem("default", false);
		if (item == nullptr || !item->value.has_value()) {
			Debug(grf, 1, "No origin warning message specified for: {}", filename);
			file->missing_warning = stredup("");
		} else {
			file->missing_warning = stredup(item->value->c_str());
		}

		file->check_result = T::CheckMD5(file, BASESET_DIR);
		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: {} (in {})", filename, full_filename);
				this->found_files++;
				break;

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

	return true;
}

template <class Tbase_set>
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 {} for base " SET_TYPE " set", filename);

	Tbase_set *set = new Tbase_set();
	IniFile *ini = new IniFile();
	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.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 {} ({}) as base " SET_TYPE " set (duplicate, {})", set->name, 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
				 * version number until a new game is started which isn't a big problem */
				if (BaseMedia<Tbase_set>::used_set == duplicate) BaseMedia<Tbase_set>::used_set = set;

				Debug(grf, 1, "Removing {} ({}) as base " SET_TYPE " set (duplicate, {})", duplicate->name, duplicate->version,
						duplicate->valid_files < set->valid_files ? "less valid files" : "lower version");
				duplicate->next = BaseMedia<Tbase_set>::duplicate_sets;
				BaseMedia<Tbase_set>::duplicate_sets = duplicate;
				ret = true;
			}
		} else {
			Tbase_set **last = &BaseMedia<Tbase_set>::available_sets;
			while (*last != nullptr) last = &(*last)->next;

			*last = set;
			ret = true;
		}
		if (ret) {
			Debug(grf, 1, "Adding {} ({}) as base " SET_TYPE " set", set->name, set->version);
		}
	} else {
		delete set;
	}

	delete ini;
	return ret;
}

/**
 * Set the set to be used.
 * @param name of the set to use
 * @return true if it could be loaded
 */
template <class Tbase_set>
/* static */ bool BaseMedia<Tbase_set>::SetSet(const std::string &name)
{
	extern void CheckExternalFiles();

	if (name.empty()) {
		if (!BaseMedia<Tbase_set>::DetermineBestSet()) return false;
		CheckExternalFiles();
		return true;
	}

	for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
		if (name == s->name) {
			BaseMedia<Tbase_set>::used_set = s;
			CheckExternalFiles();
			return true;
		}
	}
	return false;
}

/**
 * Returns a list with the sets.
 * @param p    where to print to
 * @param last the last character to print to
 * @return the last printed character
 */
template <class Tbase_set>
/* static */ char *BaseMedia<Tbase_set>::GetSetsList(char *p, const char *last)
{
	p += seprintf(p, last, "List of " SET_TYPE " sets:\n");
	for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
		p += seprintf(p, last, "%18s: %s", s->name.c_str(), s->GetDescription({}));
		int invalid = s->GetNumInvalid();
		if (invalid != 0) {
			int missing = s->GetNumMissing();
			if (missing == 0) {
				p += seprintf(p, last, " (%i corrupt file%s)\n", invalid, invalid == 1 ? "" : "s");
			} else {
				p += seprintf(p, last, " (unusable: %i missing file%s)\n", missing, missing == 1 ? "" : "s");
			}
		} else {
			p += seprintf(p, last, "\n");
		}
	}
	p += seprintf(p, last, "\n");

	return p;
}

#include "network/core/tcp_content_type.h"

template <class Tbase_set> const char *TryGetBaseSetFile(const ContentInfo *ci, bool md5sum, const Tbase_set *s)
{
	for (; s != nullptr; s = s->next) {
		if (s->GetNumMissing() != 0) continue;

		if (s->shortname != ci->unique_id) continue;
		if (!md5sum) return  s->files[0].filename;

		byte md5[16];
		memset(md5, 0, sizeof(md5));
		for (uint i = 0; i < Tbase_set::NUM_FILES; i++) {
			for (uint j = 0; j < sizeof(md5); j++) {
				md5[j] ^= s->files[i].hash[j];
			}
		}
		if (memcmp(md5, ci->md5sum, sizeof(md5)) == 0) return s->files[0].filename;
	}
	return nullptr;
}

template <class Tbase_set>
/* static */ bool BaseMedia<Tbase_set>::HasSet(const ContentInfo *ci, bool md5sum)
{
	return (TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::available_sets) != nullptr) ||
			(TryGetBaseSetFile(ci, md5sum, BaseMedia<Tbase_set>::duplicate_sets) != nullptr);
}

/**
 * Count the number of available graphics sets.
 * @return the number of sets
 */
template <class Tbase_set>
/* static */ int BaseMedia<Tbase_set>::GetNumSets()
{
	int n = 0;
	for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
		if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
		n++;
	}
	return n;
}

/**
 * Get the index of the currently active graphics set
 * @return the current set's index
 */
template <class Tbase_set>
/* static */ int BaseMedia<Tbase_set>::GetIndexOfUsedSet()
{
	int n = 0;
	for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
		if (s == BaseMedia<Tbase_set>::used_set) return n;
		if (s->GetNumMissing() != 0) continue;
		n++;
	}
	return -1;
}

/**
 * Get the name of the graphics set at the specified index
 * @return the name of the set
 */
template <class Tbase_set>
/* static */ const Tbase_set *BaseMedia<Tbase_set>::GetSet(int index)
{
	for (const Tbase_set *s = BaseMedia<Tbase_set>::available_sets; s != nullptr; s = s->next) {
		if (s != BaseMedia<Tbase_set>::used_set && s->GetNumMissing() != 0) continue;
		if (index == 0) return s;
		index--;
	}
	error("Base" SET_TYPE "::GetSet(): index %d out of range", index);
}

/**
 * Return the used set.
 * @return the used 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 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);