mirror of
https://github.com/OpenTTD/OpenTTD.git
synced 2025-07-19 10:26:27 +01:00
- Feature (small): new command-line option -c <config_file>. You can have OpenTTD load alternative config files with this setup, handy for servers, or for saves from other players if you don't want to rename your own config. This will have to do until all game-related settings are saved ingame. Couldn't find the SF patch, so credits to the guy that wrote it.
544 lines
13 KiB
C
544 lines
13 KiB
C
#include "stdafx.h"
|
|
#include "ttd.h"
|
|
#include "window.h"
|
|
#include "string.h"
|
|
#include "table/strings.h"
|
|
#include "hal.h"
|
|
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
|
|
#if defined(__linux__)
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
|
|
|
|
#if defined(WITH_SDL)
|
|
#include <SDL.h>
|
|
#endif
|
|
|
|
#ifdef __MORPHOS__
|
|
#include <exec/types.h>
|
|
ULONG __stack = (1024*1024)*2; // maybe not that much is needed actually ;)
|
|
#endif /* __MORPHOS__ */
|
|
|
|
#ifdef __AMIGA__
|
|
#warning add stack symbol to avoid that user needs to set stack manually (tokai)
|
|
// ULONG __stack =
|
|
#endif
|
|
|
|
static char *_fios_path;
|
|
static char *_fios_save_path;
|
|
static char *_fios_scn_path;
|
|
static FiosItem *_fios_items;
|
|
static int _fios_count, _fios_alloc;
|
|
|
|
static FiosItem *FiosAlloc(void)
|
|
{
|
|
if (_fios_count == _fios_alloc) {
|
|
_fios_alloc += 256;
|
|
_fios_items = realloc(_fios_items, _fios_alloc * sizeof(FiosItem));
|
|
}
|
|
return &_fios_items[_fios_count++];
|
|
}
|
|
|
|
int compare_FiosItems (const void *a, const void *b) {
|
|
const FiosItem *da = (const FiosItem *) a;
|
|
const FiosItem *db = (const FiosItem *) b;
|
|
int r;
|
|
|
|
if (_savegame_sort_order < 2) // sort by date
|
|
r = da->mtime < db->mtime ? -1 : 1;
|
|
else
|
|
r = strcasecmp(
|
|
da->title[0] != '\0' ? da->title : da->name,
|
|
db->title[0] != '\0' ? db->title : db->name
|
|
);
|
|
|
|
if (_savegame_sort_order & 1) r = -r;
|
|
return r;
|
|
}
|
|
|
|
|
|
// Get a list of savegames
|
|
FiosItem *FiosGetSavegameList(int *num, int mode)
|
|
{
|
|
FiosItem *fios;
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
struct stat sb;
|
|
int sort_start;
|
|
char filename[MAX_PATH];
|
|
|
|
if (_fios_save_path == NULL) {
|
|
_fios_save_path = malloc(MAX_PATH);
|
|
strcpy(_fios_save_path, _path.save_dir);
|
|
}
|
|
|
|
if (_game_mode == GM_EDITOR)
|
|
_fios_path = _fios_scn_path;
|
|
else
|
|
_fios_path = _fios_save_path;
|
|
|
|
// Parent directory, only if not in root already.
|
|
if (_fios_path[1] != '\0') {
|
|
fios = FiosAlloc();
|
|
fios->type = FIOS_TYPE_PARENT;
|
|
fios->mtime = 0;
|
|
strcpy(fios->title, ".. (Parent directory)");
|
|
}
|
|
|
|
// Show subdirectories first
|
|
dir = opendir(_fios_path[0] ? _fios_path : "/");
|
|
if (dir != NULL) {
|
|
while ((dirent = readdir(dir)) != NULL) {
|
|
snprintf(filename, lengthof(filename), "%s/%s",
|
|
_fios_path, dirent->d_name);
|
|
if (!stat(filename, &sb) && S_ISDIR(sb.st_mode) &&
|
|
dirent->d_name[0] != '.') {
|
|
fios = FiosAlloc();
|
|
fios->mtime = 0;
|
|
fios->type = FIOS_TYPE_DIR;
|
|
fios->title[0] = '\0';
|
|
sprintf(fios->name, "%s/ (Directory)", dirent->d_name);
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
{
|
|
/* XXX ugly global variables ... */
|
|
byte order = _savegame_sort_order;
|
|
_savegame_sort_order = 2; // sort ascending by name
|
|
qsort(_fios_items, _fios_count, sizeof(FiosItem), compare_FiosItems);
|
|
_savegame_sort_order = order;
|
|
}
|
|
|
|
// this is where to start sorting
|
|
sort_start = _fios_count;
|
|
|
|
/* 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
|
|
*/
|
|
dir = opendir(_fios_path[0] != '\0' ? _fios_path : "/");
|
|
if (dir != NULL) {
|
|
while ((dirent = readdir(dir))) {
|
|
char *t;
|
|
|
|
snprintf(filename, lengthof(filename), "%s/%s",
|
|
_fios_path, dirent->d_name);
|
|
if (stat(filename, &sb) || S_ISDIR(sb.st_mode)) continue;
|
|
|
|
t = strrchr(dirent->d_name, '.');
|
|
if (t != NULL && strcasecmp(t, ".sav") == 0) { // OpenTTD
|
|
*t = '\0'; // cut extension
|
|
fios = FiosAlloc();
|
|
fios->type = FIOS_TYPE_FILE;
|
|
fios->mtime = sb.st_mtime;
|
|
fios->title[0] = 0;
|
|
ttd_strlcpy(fios->name, dirent->d_name, sizeof(fios->name));
|
|
} else if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
|
|
int ext = 0; // start of savegame extensions in _old_extensions[]
|
|
if (t != NULL && (
|
|
(ext++, strcasecmp(t, ".ss1") == 0) ||
|
|
(ext++, strcasecmp(t, ".sv1") == 0) ||
|
|
(ext++, strcasecmp(t, ".sv2") == 0)
|
|
)) { // TTDLX(Patch)
|
|
*t = '\0'; // cut extension
|
|
fios = FiosAlloc();
|
|
fios->old_extension = ext - 1;
|
|
fios->type = FIOS_TYPE_OLDFILE;
|
|
fios->mtime = sb.st_mtime;
|
|
ttd_strlcpy(fios->name, dirent->d_name, sizeof(fios->name));
|
|
GetOldSaveGameName(fios->title, filename);
|
|
}
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
*num = _fios_count;
|
|
qsort(_fios_items + sort_start, _fios_count - sort_start, sizeof(FiosItem), compare_FiosItems);
|
|
return _fios_items;
|
|
}
|
|
|
|
// Get a list of scenarios
|
|
// FIXME: Gross code duplication with FiosGetSavegameList()
|
|
FiosItem *FiosGetScenarioList(int *num, int mode)
|
|
{
|
|
FiosItem *fios;
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
struct stat sb;
|
|
int sort_start;
|
|
char filename[MAX_PATH];
|
|
|
|
if (_fios_scn_path == NULL) {
|
|
_fios_scn_path = malloc(MAX_PATH);
|
|
strcpy(_fios_scn_path, _path.scenario_dir);
|
|
}
|
|
_fios_path = _fios_scn_path;
|
|
|
|
// Show subdirectories first
|
|
dir = opendir(_fios_path[0] ? _fios_path : "/");
|
|
if (dir != NULL) {
|
|
while ((dirent = readdir(dir)) != NULL) {
|
|
snprintf(filename, lengthof(filename), "%s/%s",
|
|
_fios_path, dirent->d_name);
|
|
if (!stat(filename, &sb) && S_ISDIR(sb.st_mode) &&
|
|
dirent->d_name[0] != '.') {
|
|
fios = FiosAlloc();
|
|
fios->mtime = '\0';
|
|
fios->type = FIOS_TYPE_DIR;
|
|
fios->title[0] = '\0';
|
|
sprintf(fios->name, "%s/ (Directory)", dirent->d_name);
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
// this is where to start sorting
|
|
sort_start = _fios_count;
|
|
|
|
/* Show scenario files
|
|
* .SCN OpenTTD style scenario file
|
|
* .SV0 Transport Tycoon Deluxe (Patch) scenario
|
|
* .SS0 Transport Tycoon Deluxe preset scenario
|
|
*/
|
|
dir = opendir(_fios_path[0] ? _fios_path : "/");
|
|
if (dir != NULL) {
|
|
while ((dirent = readdir(dir)) != NULL) {
|
|
char *t;
|
|
|
|
snprintf(filename, MAX_PATH, "%s/%s", _fios_path, dirent->d_name);
|
|
if (stat(filename, &sb) || S_ISDIR(sb.st_mode)) continue;
|
|
|
|
t = strrchr(dirent->d_name, '.');
|
|
if (t != NULL && strcasecmp(t, ".scn") != 0) { // OpenTTD
|
|
*t = '\0'; // cut extension
|
|
fios = FiosAlloc();
|
|
fios->type = FIOS_TYPE_SCENARIO;
|
|
fios->mtime = sb.st_mtime;
|
|
fios->title[0] = '\0';
|
|
ttd_strlcpy(fios->name, dirent->d_name, sizeof(fios->name)-3);
|
|
} else if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO ||
|
|
mode == SLD_NEW_GAME) {
|
|
int ext = 3; // start of scenario extensions in _old_extensions[]
|
|
if (t != NULL && (
|
|
(ext++, strcasecmp(t, ".sv0") == 0) ||
|
|
(ext++, strcasecmp(t, ".ss0") == 0)
|
|
)) {// TTDLX(Patch)
|
|
*t = '\0'; // cut extension
|
|
fios = FiosAlloc();
|
|
fios->old_extension = ext-1;
|
|
fios->type = FIOS_TYPE_OLD_SCENARIO;
|
|
fios->mtime = sb.st_mtime;
|
|
GetOldScenarioGameName(fios->title, filename);
|
|
ttd_strlcpy(fios->name, dirent->d_name, sizeof(fios->name)-3);
|
|
}
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
*num = _fios_count;
|
|
qsort(_fios_items + sort_start, _fios_count - sort_start, sizeof(FiosItem), compare_FiosItems);
|
|
return _fios_items;
|
|
}
|
|
|
|
|
|
// Free the list of savegames
|
|
void FiosFreeSavegameList(void)
|
|
{
|
|
free(_fios_items);
|
|
_fios_items = NULL;
|
|
_fios_alloc = _fios_count = 0;
|
|
}
|
|
|
|
// Browse to
|
|
char *FiosBrowseTo(const FiosItem *item)
|
|
{
|
|
static char str_buffr[512];
|
|
char *path = _fios_path;
|
|
char *s;
|
|
|
|
switch (item->type) {
|
|
case FIOS_TYPE_PARENT:
|
|
s = strrchr(path, '/');
|
|
if (s != NULL) *s = '\0';
|
|
break;
|
|
|
|
case FIOS_TYPE_DIR:
|
|
s = strchr(item->name, '/');
|
|
if (s != NULL) *s = '\0';
|
|
strcat(path, "/");
|
|
strcat(path, item->name);
|
|
break;
|
|
|
|
case FIOS_TYPE_FILE:
|
|
FiosMakeSavegameName(str_buffr, item->name);
|
|
return str_buffr;
|
|
|
|
case FIOS_TYPE_OLDFILE:
|
|
sprintf(str_buffr, "%s/%s.%s",
|
|
_fios_path, item->name, _old_extensions[item->old_extension]);
|
|
return str_buffr;
|
|
|
|
case FIOS_TYPE_SCENARIO:
|
|
sprintf(str_buffr, "%s/%s.scn", path, item->name);
|
|
return str_buffr;
|
|
|
|
case FIOS_TYPE_OLD_SCENARIO:
|
|
sprintf(str_buffr, "%s/%s.%s",
|
|
path, item->name, _old_extensions[item->old_extension]);
|
|
return str_buffr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Get descriptive texts.
|
|
// Returns a path as well as a
|
|
// string describing the path.
|
|
StringID FiosGetDescText(const char **path)
|
|
{
|
|
*path = _fios_path[0] != '\0' ? _fios_path : "/";
|
|
|
|
#if defined(__linux__)
|
|
{
|
|
struct statvfs s;
|
|
|
|
if (statvfs(*path, &s) == 0) {
|
|
uint64 tot = (uint64)s.f_bsize * s.f_bavail;
|
|
SetDParam(0, (uint32)(tot >> 20));
|
|
return STR_4005_BYTES_FREE;
|
|
} else
|
|
return STR_4006_UNABLE_TO_READ_DRIVE;
|
|
}
|
|
#else
|
|
SetDParam(0, 0);
|
|
return STR_4005_BYTES_FREE;
|
|
#endif
|
|
}
|
|
|
|
void FiosMakeSavegameName(char *buf, const char *name)
|
|
{
|
|
if (_game_mode == GM_EDITOR)
|
|
sprintf(buf, "%s/%s.scn", _fios_path, name);
|
|
else
|
|
sprintf(buf, "%s/%s.sav", _fios_path, name);
|
|
}
|
|
|
|
void FiosDelete(const char *name)
|
|
{
|
|
char path[512];
|
|
|
|
FiosMakeSavegameName(path, name);
|
|
unlink(path);
|
|
}
|
|
|
|
const DriverDesc _video_driver_descs[] = {
|
|
{"null", "Null Video Driver", &_null_video_driver, 0},
|
|
#if defined(WITH_SDL)
|
|
{ "sdl", "SDL Video Driver", &_sdl_video_driver, 1},
|
|
#endif
|
|
{ "dedicated", "Dedicated Video Driver", &_dedicated_video_driver, 0},
|
|
{ NULL, NULL, NULL, 0}
|
|
};
|
|
|
|
const DriverDesc _sound_driver_descs[] = {
|
|
{"null", "Null Sound Driver", &_null_sound_driver, 0},
|
|
#if defined(WITH_SDL)
|
|
{ "sdl", "SDL Sound Driver", &_sdl_sound_driver, 1},
|
|
#endif
|
|
{ NULL, NULL, NULL, 0}
|
|
};
|
|
|
|
#if defined(__APPLE__)
|
|
#define EXTMIDI_PRI 2
|
|
#else
|
|
#define EXTMIDI_PRI 0
|
|
#endif
|
|
|
|
const DriverDesc _music_driver_descs[] = {
|
|
#ifndef __BEOS__
|
|
#if !defined(__MORPHOS__) && !defined(__AMIGA__)
|
|
// MorphOS and AmigaOS have no music support
|
|
{"extmidi", "External MIDI Driver", &_extmidi_music_driver, EXTMIDI_PRI},
|
|
#endif
|
|
#endif
|
|
#ifdef __BEOS__
|
|
{ "bemidi", "BeOS MIDI Driver", &_bemidi_music_driver, 1},
|
|
#endif
|
|
{ "null", "Null Music Driver", &_null_music_driver, 1},
|
|
{ NULL, NULL, NULL, 0}
|
|
};
|
|
|
|
/* GetOSVersion returns the minimal required version of OS to be able to use that driver.
|
|
Not needed for *nix. */
|
|
byte GetOSVersion(void)
|
|
{
|
|
return 2; // any arbitrary number bigger than 0
|
|
// numbers lower than 2 breaks default music selection on mac
|
|
}
|
|
|
|
bool FileExists(const char *filename)
|
|
{
|
|
return access(filename, 0) == 0;
|
|
}
|
|
|
|
static int LanguageCompareFunc(const void *a, const void *b)
|
|
{
|
|
return strcmp(*(const char* const *)a, *(const char* const *)b);
|
|
}
|
|
|
|
int GetLanguageList(char **languages, int max)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
int num = 0;
|
|
|
|
dir = opendir(_path.lang_dir);
|
|
if (dir != NULL) {
|
|
while ((dirent = readdir(dir)) != NULL) {
|
|
char *t = strrchr(dirent->d_name, '.');
|
|
|
|
if (t != NULL && strcmp(t, ".lng") == 0) {
|
|
languages[num++] = strdup(dirent->d_name);
|
|
if (num == max) break;
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
qsort(languages, num, sizeof(char*), LanguageCompareFunc);
|
|
return num;
|
|
}
|
|
|
|
#if defined(__BEOS__) || defined(__linux__)
|
|
static void ChangeWorkingDirectory(char *exe)
|
|
{
|
|
char *s = strrchr(exe, '/');
|
|
if (s != NULL) {
|
|
*s = '\0';
|
|
chdir(exe);
|
|
*s = '/';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ShowInfo(const char *str)
|
|
{
|
|
puts(str);
|
|
}
|
|
|
|
void ShowOSErrorBox(const char *buf)
|
|
{
|
|
#if defined(__APPLE__)
|
|
// this creates an error in the console and then opens the console.
|
|
// Colourcodes are not used in the console, so they are skipped here
|
|
fprintf(stderr, "Error: %s", buf);
|
|
system("/Applications/Utilities/Console.app/Contents/MacOS/Console &");
|
|
#else
|
|
// all systems, but OSX
|
|
fprintf(stderr, "\033[1;31mError: %s\033[0;39m\n", buf);
|
|
#endif
|
|
}
|
|
|
|
int CDECL main(int argc, char* argv[])
|
|
{
|
|
// change the working directory to enable doubleclicking in UIs
|
|
#if defined(__BEOS__) || defined(__linux__)
|
|
ChangeWorkingDirectory(argv[0]);
|
|
#endif
|
|
|
|
_random_seeds[0][1] = _random_seeds[0][0] = time(NULL);
|
|
|
|
|
|
return ttd_main(argc, argv);
|
|
}
|
|
|
|
void DeterminePaths(void)
|
|
{
|
|
char *s;
|
|
|
|
_path.game_data_dir = malloc(MAX_PATH);
|
|
ttd_strlcpy(_path.game_data_dir, GAME_DATA_DIR, MAX_PATH);
|
|
#if defined SECOND_DATA_DIR
|
|
_path.second_data_dir = malloc(MAX_PATH);
|
|
ttd_strlcpy(_path.second_data_dir, SECOND_DATA_DIR, MAX_PATH);
|
|
#endif
|
|
|
|
#if defined(USE_HOMEDIR)
|
|
{
|
|
const char *homedir = getenv("HOME");
|
|
|
|
if (homedir == NULL) {
|
|
const struct passwd *pw = getpwuid(getuid());
|
|
if (pw != NULL) homedir = pw->pw_dir;
|
|
}
|
|
|
|
_path.personal_dir = str_fmt("%s" PATHSEP "%s", homedir, PERSONAL_DIR);
|
|
}
|
|
|
|
#else /* not defined(USE_HOMEDIR) */
|
|
|
|
_path.personal_dir = malloc(MAX_PATH);
|
|
ttd_strlcpy(_path.personal_dir, PERSONAL_DIR, MAX_PATH);
|
|
|
|
// check if absolute or relative path
|
|
s = strchr(_path.personal_dir, '/');
|
|
|
|
// add absolute path
|
|
if (s == NULL || _path.personal_dir != s) {
|
|
getcwd(_path.personal_dir, MAX_PATH);
|
|
s = strchr(_path.personal_dir, 0);
|
|
*s++ = '/';
|
|
ttd_strlcpy(s, PERSONAL_DIR, MAX_PATH);
|
|
}
|
|
|
|
#endif /* defined(USE_HOMEDIR) */
|
|
|
|
s = strchr(_path.personal_dir, 0);
|
|
|
|
// append a / ?
|
|
if (s[-1] != '/') strcpy(s, "/");
|
|
|
|
_path.save_dir = str_fmt("%ssave", _path.personal_dir);
|
|
_path.autosave_dir = str_fmt("%s/autosave", _path.save_dir);
|
|
_path.scenario_dir = str_fmt("%sscenario", _path.personal_dir);
|
|
_path.gm_dir = str_fmt("%sgm/", _path.game_data_dir);
|
|
_path.data_dir = str_fmt("%sdata/", _path.game_data_dir);
|
|
|
|
if (_config_file == NULL)
|
|
_config_file = str_fmt("%sopenttd.cfg", _path.personal_dir);
|
|
|
|
_highscore_file = str_fmt("%shs.dat", _path.personal_dir);
|
|
_log_file = str_fmt("%sopenttd.log", _path.personal_dir);
|
|
|
|
#if defined CUSTOM_LANG_DIR
|
|
// sets the search path for lng files to the custom one
|
|
_path.lang_dir = malloc( MAX_PATH );
|
|
ttd_strlcpy( _path.lang_dir, CUSTOM_LANG_DIR, MAX_PATH);
|
|
#else
|
|
_path.lang_dir = str_fmt("%slang/", _path.game_data_dir);
|
|
#endif
|
|
|
|
// create necessary folders
|
|
mkdir(_path.personal_dir, 0755);
|
|
mkdir(_path.save_dir, 0755);
|
|
mkdir(_path.autosave_dir, 0755);
|
|
mkdir(_path.scenario_dir, 0755);
|
|
}
|
|
|
|
bool InsertTextBufferClipboard(Textbuf *tb)
|
|
{
|
|
return false;
|
|
}
|