Esempio n. 1
0
const char *SaveKeycodes(const Hotkey<T> *hotkey)
{
	static char buf[128];
	buf[0] = '\0';
	for (uint i = 0; i < hotkey->keycodes.Length(); i++) {
		const char *str = KeycodeToString(hotkey->keycodes[i]);
		if (i > 0) strecat(buf, ",", lastof(buf));
		strecat(buf, str, lastof(buf));
	}
	return buf;
}
Esempio n. 2
0
void DetermineBasePaths(const char *exe)
{
	char tmp[MAX_PATH];
	TCHAR path[MAX_PATH];
#ifdef WITH_PERSONAL_DIR
	if (SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path))) {
		strecpy(tmp, FS2OTTD(path), lastof(tmp));
		AppendPathSeparator(tmp, lastof(tmp));
		strecat(tmp, PERSONAL_DIR, lastof(tmp));
		AppendPathSeparator(tmp, lastof(tmp));
		_searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
	} else {
		_searchpaths[SP_PERSONAL_DIR] = NULL;
	}

	if (SUCCEEDED(OTTDSHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path))) {
		strecpy(tmp, FS2OTTD(path), lastof(tmp));
		AppendPathSeparator(tmp, lastof(tmp));
		strecat(tmp, PERSONAL_DIR, lastof(tmp));
		AppendPathSeparator(tmp, lastof(tmp));
		_searchpaths[SP_SHARED_DIR] = stredup(tmp);
	} else {
		_searchpaths[SP_SHARED_DIR] = NULL;
	}
#else
	_searchpaths[SP_PERSONAL_DIR] = NULL;
	_searchpaths[SP_SHARED_DIR]   = NULL;
#endif

	/* Get the path to working directory of OpenTTD */
	getcwd(tmp, lengthof(tmp));
	AppendPathSeparator(tmp, lastof(tmp));
	_searchpaths[SP_WORKING_DIR] = stredup(tmp);

	if (!GetModuleFileName(NULL, path, lengthof(path))) {
		DEBUG(misc, 0, "GetModuleFileName failed (%lu)\n", GetLastError());
		_searchpaths[SP_BINARY_DIR] = NULL;
	} else {
		TCHAR exec_dir[MAX_PATH];
		_tcsncpy(path, convert_to_fs(exe, path, lengthof(path)), lengthof(path));
		if (!GetFullPathName(path, lengthof(exec_dir), exec_dir, NULL)) {
			DEBUG(misc, 0, "GetFullPathName failed (%lu)\n", GetLastError());
			_searchpaths[SP_BINARY_DIR] = NULL;
		} else {
			strecpy(tmp, convert_from_fs(exec_dir, tmp, lengthof(tmp)), lastof(tmp));
			char *s = strrchr(tmp, PATHSEPCHAR);
			*(s + 1) = '\0';
			_searchpaths[SP_BINARY_DIR] = stredup(tmp);
		}
	}

	_searchpaths[SP_INSTALLATION_DIR]       = NULL;
	_searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
}
Esempio n. 3
0
/**
 * Create an autosave. The default name is "autosave#.sav". However with
 * the setting 'keep_all_autosave' the name defaults to company-name + date
 */
static void DoAutosave()
{
	char buf[MAX_PATH];

#if defined(PSP)
	/* Autosaving in networking is too time expensive for the PSP */
	if (_networking) return;
#endif /* PSP */

	if (_settings_client.gui.keep_all_autosave) {
		GenerateDefaultSaveName(buf, lastof(buf));
		strecat(buf, ".sav", lastof(buf));
	} else {
		static int _autosave_ctr = 0;

		/* generate a savegame name and number according to _settings_client.gui.max_num_autosaves */
		snprintf(buf, sizeof(buf), "autosave%d.sav", _autosave_ctr);

		if (++_autosave_ctr >= _settings_client.gui.max_num_autosaves) _autosave_ctr = 0;
	}

	DEBUG(sl, 2, "Autosaving to '%s'", buf);
	if (SaveOrLoad(buf, SL_SAVE, AUTOSAVE_DIR) != SL_OK) {
		ShowErrorMessage(STR_ERROR_AUTOSAVE_FAILED, INVALID_STRING_ID, WL_ERROR);
	}
}
Esempio n. 4
0
/** Print out the current debug-level
 * Just return a string with the values of all the debug categorites
 * @return string with debug-levels
 */
const char *GetDebugString()
{
	const DebugLevel *i;
	static char dbgstr[150];
	char dbgval[20];

	memset(dbgstr, 0, sizeof(dbgstr));
	i = debug_level;
	snprintf(dbgstr, sizeof(dbgstr), "%s=%d", i->name, *i->level);

	for (i++; i != endof(debug_level); i++) {
		snprintf(dbgval, sizeof(dbgval), ", %s=%d", i->name, *i->level);
		strecat(dbgstr, dbgval, lastof(dbgstr));
	}

	return dbgstr;
}
Esempio n. 5
0
void Squirrel::PrintFunc(HSQUIRRELVM vm, const SQChar *s, ...)
{
	va_list arglist;
	SQChar buf[1024];

	va_start(arglist, s);
	vseprintf(buf, lastof(buf) - 2, s, arglist);
	va_end(arglist);
	strecat(buf, "\n", lastof(buf));

	/* Check if we have a custom print function */
	SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
	if (func == NULL) {
		printf("%s", buf);
	} else {
		(*func)(false, buf);
	}
}
/**
 * Clone the custom name of a vehicle, adding or incrementing a number.
 * @param src Source vehicle, with a custom name.
 * @param dst Destination vehicle.
 */
static void CloneVehicleName(const Vehicle *src, Vehicle *dst)
{
	char buf[256];

	/* Find the position of the first digit in the last group of digits. */
	size_t number_position;
	for (number_position = strlen(src->name); number_position > 0; number_position--) {
		/* The design of UTF-8 lets this work simply without having to check
		 * for UTF-8 sequences. */
		if (src->name[number_position - 1] < '0' || src->name[number_position - 1] > '9') break;
	}

	/* Format buffer and determine starting number. */
	int num;
	byte padding = 0;
	if (number_position == strlen(src->name)) {
		/* No digit at the end, so start at number 2. */
		strecpy(buf, src->name, lastof(buf));
		strecat(buf, " ", lastof(buf));
		number_position = strlen(buf);
		num = 2;
	} else {
		/* Found digits, parse them and start at the next number. */
		strecpy(buf, src->name, lastof(buf));
		buf[number_position] = '\0';
		char *endptr;
		num = strtol(&src->name[number_position], &endptr, 10) + 1;
		padding = endptr - &src->name[number_position];
	}

	/* Check if this name is already taken. */
	for (int max_iterations = 1000; max_iterations > 0; max_iterations--, num++) {
		/* Attach the number to the temporary name. */
		seprintf(&buf[number_position], lastof(buf), "%0*d", padding, num);

		/* Check the name is unique. */
		if (IsUniqueVehicleName(buf)) {
			dst->name = strdup(buf);
			break;
		}
	}

	/* All done. If we didn't find a name, it'll just use its default. */
}
/**
 * Determine the full filename of a piece of content information
 * @param ci         the information to get the filename from
 * @param compressed should the filename end with .gz?
 * @return a statically allocated buffer with the filename or
 *         NULL when no filename could be made.
 */
static char *GetFullFilename(const ContentInfo *ci, bool compressed)
{
	Subdirectory dir;
	switch (ci->type) {
		default: return NULL;
		case CONTENT_TYPE_BASE_GRAPHICS: dir = DATA_DIR;       break;
		case CONTENT_TYPE_BASE_MUSIC:    dir = GM_DIR;         break;
		case CONTENT_TYPE_BASE_SOUNDS:   dir = DATA_DIR;       break;
		case CONTENT_TYPE_NEWGRF:        dir = DATA_DIR;       break;
		case CONTENT_TYPE_AI:            dir = AI_DIR;         break;
		case CONTENT_TYPE_AI_LIBRARY:    dir = AI_LIBRARY_DIR; break;
		case CONTENT_TYPE_SCENARIO:      dir = SCENARIO_DIR;   break;
		case CONTENT_TYPE_HEIGHTMAP:     dir = HEIGHTMAP_DIR;  break;
	}

	static char buf[MAX_PATH];
	FioGetFullPath(buf, lengthof(buf), SP_AUTODOWNLOAD_DIR, dir, ci->filename);
	strecat(buf, compressed ? ".tar.gz" : ".tar", lastof(buf));

	return buf;
}
Esempio n. 8
0
/**
 * Create an autosave. The default name is "autosave#.sav". However with
 * the setting 'keep_all_autosave' the name defaults to company-name + date
 */
static void DoAutosave()
{
	char buf[MAX_PATH];

	if (_settings_client.gui.keep_all_autosave) {
		GenerateDefaultSaveName(buf, lastof(buf));
		strecat(buf, ".sav", lastof(buf));
	} else {
		static int _autosave_ctr = 0;

		/* generate a savegame name and number according to _settings_client.gui.max_num_autosaves */
		seprintf(buf, lastof(buf), "autosave%d.sav", _autosave_ctr);

		if (++_autosave_ctr >= _settings_client.gui.max_num_autosaves) _autosave_ctr = 0;
	}

	DEBUG(sl, 2, "Autosaving to '%s'", buf);
	if (SaveOrLoad(buf, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR) != SL_OK) {
		ShowErrorMessage(STR_ERROR_AUTOSAVE_FAILED, INVALID_STRING_ID, WL_ERROR);
	}
}
static char *RandomPart(char *buf, GRFTownName *t, uint32 seed, byte id, const char *last)
{
	assert(t != NULL);
	for (int i = 0; i < t->nbparts[id]; i++) {
		byte count = t->partlist[id][i].bitcount;
		uint16 maxprob = t->partlist[id][i].maxprob;
		uint32 r = (GB(seed, t->partlist[id][i].bitstart, count) * maxprob) >> count;
		for (int j = 0; j < t->partlist[id][i].partcount; j++) {
			byte prob = t->partlist[id][i].parts[j].prob;
			maxprob -= GB(prob, 0, 7);
			if (maxprob > r) continue;
			if (HasBit(prob, 7)) {
				buf = RandomPart(buf, t, seed, t->partlist[id][i].parts[j].data.id, last);
			} else {
				buf = strecat(buf, t->partlist[id][i].parts[j].data.text, last);
			}
			break;
		}
	}
	return buf;
}
Esempio n. 10
0
/**
 * Convert a hotkey to it's string representation so it can be written to the
 * config file. Seperate parts of the keycode (like "CTRL" and "F1" are split
 * by a '+'.
 * @param keycode The keycode to convert to a string.
 * @return A string representation of this keycode.
 * @note The return value is a static buffer, strdup the result before calling
 *  this function again.
 */
static const char *KeycodeToString(uint16 keycode)
{
	static char buf[32];
	buf[0] = '\0';
	bool first = true;
	if (keycode & WKC_GLOBAL_HOTKEY) {
		strecat(buf, "GLOBAL", lastof(buf));
		first = false;
	}
	if (keycode & WKC_SHIFT) {
		if (!first) strecat(buf, "+", lastof(buf));
		strecat(buf, "SHIFT", lastof(buf));
		first = false;
	}
	if (keycode & WKC_CTRL) {
		if (!first) strecat(buf, "+", lastof(buf));
		strecat(buf, "CTRL", lastof(buf));
		first = false;
	}
	if (keycode & WKC_ALT) {
		if (!first) strecat(buf, "+", lastof(buf));
		strecat(buf, "ALT", lastof(buf));
		first = false;
	}
	if (keycode & WKC_META) {
		if (!first) strecat(buf, "+", lastof(buf));
		strecat(buf, "META", lastof(buf));
		first = false;
	}
	if (!first) strecat(buf, "+", lastof(buf));
	keycode = keycode & ~WKC_SPECIAL_KEYS;

	for (uint i = 0; i < lengthof(_keycode_to_name); i++) {
		if (_keycode_to_name[i].keycode == keycode) {
			strecat(buf, _keycode_to_name[i].name, lastof(buf));
			return buf;
		}
	}
	assert(keycode < 128);
	char key[2];
	key[0] = keycode;
	key[1] = '\0';
	strecat(buf, key, lastof(buf));
	return buf;
}
Esempio n. 11
0
/**
 * Save the Ini file's data to the disk.
 * @param filename the file to save to.
 * @return true if saving succeeded.
 */
bool IniFile::SaveToDisk(const char *filename)
{
	/*
	 * First write the configuration to a (temporary) file and then rename
	 * that file. This to prevent that when OpenTTD crashes during the save
	 * you end up with a truncated configuration file.
	 */
	char file_new[MAX_PATH];

	strecpy(file_new, filename, lastof(file_new));
	strecat(file_new, ".new", lastof(file_new));
	FILE *f = fopen(file_new, "w");
	if (f == NULL) return false;

	for (const IniGroup *group = this->group; group != NULL; group = group->next) {
		if (group->comment) fputs(group->comment, f);
		fprintf(f, "[%s]\n", group->name);
		for (const IniItem *item = group->item; item != NULL; item = item->next) {
			if (item->comment != NULL) fputs(item->comment, f);

			/* protect item->name with quotes if needed */
			if (strchr(item->name, ' ') != NULL ||
					item->name[0] == '[') {
				fprintf(f, "\"%s\"", item->name);
			} else {
				fprintf(f, "%s", item->name);
			}

			fprintf(f, " = %s\n", item->value == NULL ? "" : item->value);
		}
	}
	if (this->comment) fputs(this->comment, f);

/*
 * POSIX (and friends) do not guarantee that when a file is closed it is
 * flushed to the disk. So we manually flush it do disk if we have the
 * APIs to do so. We only need to flush the data as the metadata itself
 * (modification date etc.) is not important to us; only the real data is.
 */
#ifdef WITH_FDATASYNC
	int ret = fdatasync(fileno(f));
	fclose(f);
	if (ret != 0) return false;
#else
	fclose(f);
#endif

#if defined(WIN32) || defined(WIN64)
	/* _tcsncpy = strcpy is TCHAR is char, but isn't when TCHAR is wchar. */
	#undef strncpy
	/* Allocate space for one more \0 character. */
	TCHAR tfilename[MAX_PATH + 1], tfile_new[MAX_PATH + 1];
	_tcsncpy(tfilename, OTTD2FS(filename), MAX_PATH);
	_tcsncpy(tfile_new, OTTD2FS(file_new), MAX_PATH);
	/* SHFileOperation wants a double '\0' terminated string. */
	tfilename[MAX_PATH - 1] = '\0';
	tfile_new[MAX_PATH - 1] = '\0';
	tfilename[_tcslen(tfilename) + 1] = '\0';
	tfile_new[_tcslen(tfile_new) + 1] = '\0';

	/* Rename file without any user confirmation. */
	SHFILEOPSTRUCT shfopt;
	MemSetT(&shfopt, 0);
	shfopt.wFunc  = FO_MOVE;
	shfopt.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_SILENT;
	shfopt.pFrom  = tfile_new;
	shfopt.pTo    = tfilename;
	SHFileOperation(&shfopt);
#else
	if (rename(file_new, filename) < 0) {
		DEBUG(misc, 0, "Renaming %s to %s failed; configuration not saved", file_new, filename);
	}
#endif

	return true;
}
Esempio n. 12
0
/**
 * Draw the details for the given vehicle at the given position
 *
 * @param v     current vehicle
 * @param left  The left most coordinate to draw
 * @param right The right most coordinate to draw
 * @param y     The y coordinate
 */
void DrawRoadVehDetails(const Vehicle *v, int left, int right, int y)
{
	const RoadVehicle *rv = RoadVehicle::From(v);

	uint y_offset = rv->HasArticulatedPart() ? 15 : 0; // Draw the first line below the sprite of an articulated RV instead of after it.
	StringID str;
	Money feeder_share = 0;

	SetDParam(0, v->engine_type);
	SetDParam(1, v->build_year);
	SetDParam(2, v->value);
	DrawString(left, right, y + y_offset, STR_VEHICLE_INFO_BUILT_VALUE, TC_FROMSTRING, SA_LEFT | SA_STRIP);

	if (rv->HasArticulatedPart()) {
		CargoArray max_cargo;
		StringID subtype_text[NUM_CARGO];
		char capacity[512];

		memset(subtype_text, 0, sizeof(subtype_text));

		for (const Vehicle *u = v; u != NULL; u = u->Next()) {
			max_cargo[u->cargo_type] += u->cargo_cap;
			if (u->cargo_cap > 0) {
				StringID text = GetCargoSubtypeText(u);
				if (text != STR_EMPTY) subtype_text[u->cargo_type] = text;
			}
		}

		GetString(capacity, STR_VEHICLE_DETAILS_TRAIN_ARTICULATED_RV_CAPACITY, lastof(capacity));

		bool first = true;
		for (CargoID i = 0; i < NUM_CARGO; i++) {
			if (max_cargo[i] > 0) {
				char buffer[128];

				SetDParam(0, i);
				SetDParam(1, max_cargo[i]);
				GetString(buffer, STR_JUST_CARGO, lastof(buffer));

				if (!first) strecat(capacity, ", ", lastof(capacity));
				strecat(capacity, buffer, lastof(capacity));

				if (subtype_text[i] != 0) {
					GetString(buffer, subtype_text[i], lastof(buffer));
					strecat(capacity, buffer, lastof(capacity));
				}

				first = false;
			}
		}

		DrawString(left, right, y + FONT_HEIGHT_NORMAL + y_offset, capacity, TC_BLUE);

		for (const Vehicle *u = v; u != NULL; u = u->Next()) {
			if (u->cargo_cap == 0) continue;

			str = STR_VEHICLE_DETAILS_CARGO_EMPTY;
			if (!u->cargo.Empty()) {
				SetDParam(0, u->cargo_type);
				SetDParam(1, u->cargo.Count());
				SetDParam(2, u->cargo.Source());
				str = STR_VEHICLE_DETAILS_CARGO_FROM;
				feeder_share += u->cargo.FeederShare();
			}
			DrawString(left, right, y + 2 * FONT_HEIGHT_NORMAL + 1 + y_offset, str);

			y_offset += FONT_HEIGHT_NORMAL + 1;
		}

		y_offset -= FONT_HEIGHT_NORMAL + 1;
	} else {
		SetDParam(0, v->cargo_type);
		SetDParam(1, v->cargo_cap);
		SetDParam(4, GetCargoSubtypeText(v));
		DrawString(left, right, y + FONT_HEIGHT_NORMAL + y_offset, STR_VEHICLE_INFO_CAPACITY);

		str = STR_VEHICLE_DETAILS_CARGO_EMPTY;
		if (!v->cargo.Empty()) {
			SetDParam(0, v->cargo_type);
			SetDParam(1, v->cargo.Count());
			SetDParam(2, v->cargo.Source());
			str = STR_VEHICLE_DETAILS_CARGO_FROM;
			feeder_share += v->cargo.FeederShare();
		}
		DrawString(left, right, y + 2 * FONT_HEIGHT_NORMAL + 1 + y_offset, str);
	}

	/* Draw Transfer credits text */
	SetDParam(0, feeder_share);
	DrawString(left, right, y + 3 * FONT_HEIGHT_NORMAL + 3 + y_offset, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE);
}
Esempio n. 13
0
DEF_UDP_RECEIVE_COMMAND(Client, PACKET_UDP_SERVER_RESPONSE)
{
	NetworkGameList *item;

	/* Just a fail-safe.. should never happen */
	if (_network_udp_server) return;

	DEBUG(net, 4, "[udp] server response from %s", client_addr->GetAddressAsString());

	/* Find next item */
	item = NetworkGameListAddItem(*client_addr);

	this->Recv_NetworkGameInfo(p, &item->info);

	item->info.compatible = true;
	{
		/* Checks whether there needs to be a request for names of GRFs and makes
		 * the request if necessary. GRFs that need to be requested are the GRFs
		 * that do not exist on the clients system and we do not have the name
		 * resolved of, i.e. the name is still UNKNOWN_GRF_NAME_PLACEHOLDER.
		 * The in_request array and in_request_count are used so there is no need
		 * to do a second loop over the GRF list, which can be relatively expensive
		 * due to the string comparisons. */
		const GRFConfig *in_request[NETWORK_MAX_GRF_COUNT];
		const GRFConfig *c;
		uint in_request_count = 0;

		for (c = item->info.grfconfig; c != NULL; c = c->next) {
			if (c->status == GCS_NOT_FOUND) item->info.compatible = false;
			if (c->status != GCS_NOT_FOUND || strcmp(c->name, UNKNOWN_GRF_NAME_PLACEHOLDER) != 0) continue;
			in_request[in_request_count] = c;
			in_request_count++;
		}

		if (in_request_count > 0) {
			/* There are 'unknown' GRFs, now send a request for them */
			uint i;
			Packet packet(PACKET_UDP_CLIENT_GET_NEWGRFS);

			packet.Send_uint8(in_request_count);
			for (i = 0; i < in_request_count; i++) {
				this->Send_GRFIdentifier(&packet, in_request[i]);
			}

			this->SendPacket(&packet, &item->address);
		}
	}

	if (item->info.hostname[0] == '\0') {
		snprintf(item->info.hostname, sizeof(item->info.hostname), "%s", client_addr->GetHostname());
	}

	if (client_addr->GetAddress()->ss_family == AF_INET6) {
		strecat(item->info.server_name, " (IPv6)", lastof(item->info.server_name));
	}

	/* Check if we are allowed on this server based on the revision-match */
	item->info.version_compatible = IsNetworkCompatibleVersion(item->info.server_revision);
	item->info.compatible &= item->info.version_compatible; // Already contains match for GRFs

	item->online = true;

	UpdateNetworkGameWindow(false);
}
Esempio n. 14
0
/**
 * 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)
{
	const char *ext = strrchr(filename, '.');
	if (ext == NULL) return false;

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

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

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

	FiosItem *fios = _fios_items.Append();
#ifdef WIN32
	struct _stat sb;
	if (_tstat(OTTD2FS(filename), &sb) == 0) {
#else
	struct stat sb;
	if (stat(filename, &sb) == 0) {
#endif
		fios->mtime = sb.st_mtime;
	} else {
		fios->mtime = 0;
	}

	fios->type = type;
	strecpy(fios->name, filename, 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 == NULL) ? filename : (t + 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 mode The mode we are in. Some modes don't allow 'parent'.
 *  @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.
 */
static void FiosGetFileList(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc, Subdirectory subdir)
{
	struct stat sb;
	struct dirent *dirent;
	DIR *dir;
	FiosItem *fios;
	int sort_start;
	char d_name[sizeof(fios->name)];

	_fios_items.Clear();

	/* A parent directory link exists if we are not in the root directory */
	if (!FiosIsRoot(_fios_path)) {
		fios = _fios_items.Append();
		fios->type = FIOS_TYPE_PARENT;
		fios->mtime = 0;
		strecpy(fios->name, "..", lastof(fios->name));
		strecpy(fios->title, ".. (Parent directory)", lastof(fios->title));
	}

	/* Show subdirectories */
	if ((dir = ttd_opendir(_fios_path)) != NULL) {
		while ((dirent = readdir(dir)) != NULL) {
			strecpy(d_name, FS2OTTD(dirent->d_name), lastof(d_name));

			/* found file must be directory, but not '.' or '..' */
			if (FiosIsValidFile(_fios_path, dirent, &sb) && S_ISDIR(sb.st_mode) &&
					(!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
					strcmp(d_name, ".") != 0 && strcmp(d_name, "..") != 0) {
				fios = _fios_items.Append();
				fios->type = FIOS_TYPE_DIR;
				fios->mtime = 0;
				strecpy(fios->name, d_name, lastof(fios->name));
				snprintf(fios->title, lengthof(fios->title), "%s" PATHSEP " (Directory)", d_name);
				str_validate(fios->title, lastof(fios->title));
			}
		}
		closedir(dir);
	}

	/* Sort the subdirs always by name, ascending, remember user-sorting order */
	{
		SortingBits order = _savegame_sort_order;
		_savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
		QSortT(_fios_items.Begin(), _fios_items.Length(), CompareFiosItems);
		_savegame_sort_order = order;
	}

	/* This is where to start sorting for the filenames */
	sort_start = _fios_items.Length();

	/* Show files */
	FiosFileScanner scanner(mode, callback_proc);
	if (subdir == NO_DIRECTORY) {
		scanner.Scan(NULL, _fios_path, false);
	} else {
		scanner.Scan(NULL, subdir, true, true);
	}

	QSortT(_fios_items.Get(sort_start), _fios_items.Length() - sort_start, CompareFiosItems);

	/* Show drives */
	FiosGetDrives();

	_fios_items.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)
{
	char buf[MAX_PATH];
	strecpy(buf, file, lastof(buf));
	strecat(buf, ".title", lastof(buf));

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

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