char *filename = getScoreTapeFilename(tape.score_tape_basename, nr);
// used instead of "leveldir_current->subdir" (for network games)
- InitScoreTapeDirectory(levelset.identifier, nr);
+ InitScoreDirectory(levelset.identifier);
SaveTapeExt(filename);
}
Print("[tas_keys]");
if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS)
Print("[small_graphics]");
-
Print("\n");
+
+ int year2 = tape->date / 10000;
+ int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2);
+ int month_index_raw = (tape->date / 100) % 100;
+ int month_index = month_index_raw % 12; // prevent invalid index
+ int month = month_index + 1;
+ int day = tape->date % 100;
+
+ Print("Tape date: %04d-%02d-%02d\n", year4, month, day);
+
PrintLine("-", 79);
tape_frame_counter = 0;
void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data,
char *error)
{
+#if defined(PLATFORM_EMSCRIPTEN)
+ // threads currently not fully supported by Emscripten/SDL and some browsers
+ function(data);
+#else
SDL_Thread *thread = SDL_CreateThread(function, name, data);
if (thread != NULL)
// nasty kludge to lower probability of intermingled thread error messages
Delay(1);
+#endif
}
char *getPasswordJSON(char *password)
return password_json;
}
-static void DownloadServerScoreToCacheExt(struct HttpRequest *request,
- struct HttpResponse *response,
- int level_nr,
- char *score_cache_filename)
+struct ApiGetScoreThreadData
{
+ int level_nr;
+ char *score_cache_filename;
+};
+
+static void *CreateThreadData_ApiGetScore(int nr)
+{
+ struct ApiGetScoreThreadData *data =
+ checked_malloc(sizeof(struct ApiGetScoreThreadData));
+ char *score_cache_filename = getScoreCacheFilename(nr);
+
+ data->level_nr = nr;
+ data->score_cache_filename = getStringCopy(score_cache_filename);
+
+ return data;
+}
+
+static void FreeThreadData_ApiGetScore(void *data_raw)
+{
+ struct ApiGetScoreThreadData *data = data_raw;
+
+ checked_free(data->score_cache_filename);
+ checked_free(data);
+}
+
+static boolean SetRequest_ApiGetScore(struct HttpRequest *request,
+ void *data_raw)
+{
+ struct ApiGetScoreThreadData *data = data_raw;
+ int level_nr = data->level_nr;
+
request->hostname = setup.api_server_hostname;
request->port = API_SERVER_PORT;
request->method = API_SERVER_METHOD;
request->uri = API_SERVER_URI_GET;
+ char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
+ char *levelset_name = getEscapedJSON(leveldir_current->name);
+
snprintf(request->body, MAX_HTTP_BODY_SIZE,
"{\n"
"%s"
" \"game_version\": \"%s\",\n"
" \"game_platform\": \"%s\",\n"
" \"levelset_identifier\": \"%s\",\n"
+ " \"levelset_name\": \"%s\",\n"
" \"level_nr\": \"%d\"\n"
"}\n",
getPasswordJSON(setup.api_server_password),
getProgramRealVersionString(),
getProgramPlatformString(),
- levelset.identifier, level_nr);
-
- ConvertHttpRequestBodyToServerEncoding(request);
+ levelset_identifier,
+ levelset_name,
+ level_nr);
- if (!DoHttpRequest(request, response))
- {
- Error("HTTP request failed: %s", GetHttpError());
+ checked_free(levelset_identifier);
+ checked_free(levelset_name);
- return;
- }
+ ConvertHttpRequestBodyToServerEncoding(request);
- if (!HTTP_SUCCESS(response->status_code))
- {
- Error("server failed to handle request: %d %s",
- response->status_code,
- response->status_text);
+ return TRUE;
+}
- return;
- }
+static void HandleResponse_ApiGetScore(struct HttpResponse *response,
+ void *data_raw)
+{
+ struct ApiGetScoreThreadData *data = data_raw;
if (response->body_size == 0)
{
ConvertHttpResponseBodyToClientEncoding(response);
- char *filename = score_cache_filename;
+ char *filename = data->score_cache_filename;
FILE *file;
int i;
server_scores.updated = TRUE;
}
-static void DownloadServerScoreToCache(int level_nr, char *score_cache_filename)
+#if defined(PLATFORM_EMSCRIPTEN)
+static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw,
+ void *buffer, unsigned int size)
{
- struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
- struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+ struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
- DownloadServerScoreToCacheExt(request, response,
- level_nr, score_cache_filename);
+ if (response != NULL)
+ {
+ HandleResponse_ApiGetScore(response, data_raw);
- checked_free(request);
- checked_free(response);
+ checked_free(response);
+ }
+ else
+ {
+ Error("server response too large to handle (%d bytes)", size);
+ }
+
+ FreeThreadData_ApiGetScore(data_raw);
}
-struct DownloadServerScoreToCacheThreadData
+static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw,
+ int code, const char *status)
{
- int level_nr;
- char *score_cache_filename;
-};
+ Error("server failed to handle request: %d %s", code, status);
-static int DownloadServerScoreToCacheThread(void *data_raw)
+ FreeThreadData_ApiGetScore(data_raw);
+}
+
+static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw,
+ int bytes, int size)
{
- struct DownloadServerScoreToCacheThreadData *data = data_raw;
+ // nothing to do here
+}
- DownloadServerScoreToCache(data->level_nr,
- data->score_cache_filename);
+static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request,
+ void *data_raw)
+{
+ if (!SetRequest_ApiGetScore(request, data_raw))
+ {
+ FreeThreadData_ApiGetScore(data_raw);
- checked_free(data->score_cache_filename);
- checked_free(data);
+ return;
+ }
- return 0;
+ emscripten_async_wget2_data(request->uri,
+ request->method,
+ request->body,
+ data_raw,
+ TRUE,
+ Emscripten_ApiGetScore_Loaded,
+ Emscripten_ApiGetScore_Failed,
+ Emscripten_ApiGetScore_Progress);
}
-static void DownloadServerScoreToCacheAsThread(int nr)
+#else
+
+static void ApiGetScore_HttpRequestExt(struct HttpRequest *request,
+ struct HttpResponse *response,
+ void *data_raw)
{
- struct DownloadServerScoreToCacheThreadData *data =
- checked_malloc(sizeof(struct DownloadServerScoreToCacheThreadData));
- char *score_cache_filename = getScoreCacheFilename(nr);
+ if (!SetRequest_ApiGetScore(request, data_raw))
+ return;
- data->level_nr = nr;
- data->score_cache_filename = getStringCopy(score_cache_filename);
+ if (!DoHttpRequest(request, response))
+ {
+ Error("HTTP request failed: %s", GetHttpError());
+
+ return;
+ }
+
+ if (!HTTP_SUCCESS(response->status_code))
+ {
+ // do not show error message if no scores found for this level set
+ if (response->status_code == 404)
+ return;
- ExecuteAsThread(DownloadServerScoreToCacheThread,
- "DownloadServerScoreToCache", data,
+ Error("server failed to handle request: %d %s",
+ response->status_code,
+ response->status_text);
+
+ return;
+ }
+
+ HandleResponse_ApiGetScore(response, data_raw);
+}
+
+static void ApiGetScore_HttpRequest(struct HttpRequest *request,
+ struct HttpResponse *response,
+ void *data_raw)
+{
+ ApiGetScore_HttpRequestExt(request, response, data_raw);
+
+ FreeThreadData_ApiGetScore(data_raw);
+}
+#endif
+
+static int ApiGetScoreThread(void *data_raw)
+{
+ struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
+ struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+
+#if defined(PLATFORM_EMSCRIPTEN)
+ Emscripten_ApiGetScore_HttpRequest(request, data_raw);
+#else
+ ApiGetScore_HttpRequest(request, response, data_raw);
+#endif
+
+ checked_free(request);
+ checked_free(response);
+
+ return 0;
+}
+
+static void ApiGetScoreAsThread(int nr)
+{
+ struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr);
+
+ ExecuteAsThread(ApiGetScoreThread,
+ "ApiGetScore", data,
"download scores from server");
}
{
// 2nd step: download server scores from score server to cache file
// (as thread, as it might time out if the server is not reachable)
- DownloadServerScoreToCacheAsThread(nr);
+ ApiGetScoreAsThread(nr);
}
}
return buffer_encoded;
}
-static void UploadScoreToServerExt(struct HttpRequest *request,
- struct HttpResponse *response,
- int level_nr,
- char *score_tape_filename,
- struct ScoreEntry *score_entry)
+struct ApiAddScoreThreadData
+{
+ int level_nr;
+ boolean tape_saved;
+ char *score_tape_filename;
+ struct ScoreEntry score_entry;
+};
+
+static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved,
+ char *score_tape_filename)
+{
+ struct ApiAddScoreThreadData *data =
+ checked_malloc(sizeof(struct ApiAddScoreThreadData));
+ struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
+
+ if (score_tape_filename == NULL)
+ score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
+
+ data->level_nr = nr;
+ data->tape_saved = tape_saved;
+ data->score_entry = *score_entry;
+ data->score_tape_filename = getStringCopy(score_tape_filename);
+
+ return data;
+}
+
+static void FreeThreadData_ApiAddScore(void *data_raw)
{
+ struct ApiAddScoreThreadData *data = data_raw;
+
+ checked_free(data->score_tape_filename);
+ checked_free(data);
+}
+
+static boolean SetRequest_ApiAddScore(struct HttpRequest *request,
+ void *data_raw)
+{
+ struct ApiAddScoreThreadData *data = data_raw;
+ struct ScoreEntry *score_entry = &data->score_entry;
+ char *score_tape_filename = data->score_tape_filename;
+ boolean tape_saved = data->tape_saved;
+ int level_nr = data->level_nr;
+
request->hostname = setup.api_server_hostname;
request->port = API_SERVER_PORT;
request->method = API_SERVER_METHOD;
{
Error("loading and base64 encoding score tape file failed");
- return;
+ return FALSE;
}
char *player_name_raw = score_entry->name;
" \"score\": \"%d\",\n"
" \"time\": \"%d\",\n"
" \"tape_basename\": \"%s\",\n"
+ " \"tape_saved\": \"%d\",\n"
" \"tape\": \"%s\"\n"
"}\n",
getPasswordJSON(setup.api_server_password),
score_entry->score,
score_entry->time,
score_entry->tape_basename,
+ tape_saved,
tape_base64);
checked_free(tape_base64);
ConvertHttpRequestBodyToServerEncoding(request);
+ return TRUE;
+}
+
+static void HandleResponse_ApiAddScore(struct HttpResponse *response,
+ void *data_raw)
+{
+ server_scores.uploaded = TRUE;
+}
+
+#if defined(PLATFORM_EMSCRIPTEN)
+static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw,
+ void *buffer, unsigned int size)
+{
+ struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
+
+ if (response != NULL)
+ {
+ HandleResponse_ApiAddScore(response, data_raw);
+
+ checked_free(response);
+ }
+ else
+ {
+ Error("server response too large to handle (%d bytes)", size);
+ }
+
+ FreeThreadData_ApiAddScore(data_raw);
+}
+
+static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw,
+ int code, const char *status)
+{
+ Error("server failed to handle request: %d %s", code, status);
+
+ FreeThreadData_ApiAddScore(data_raw);
+}
+
+static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw,
+ int bytes, int size)
+{
+ // nothing to do here
+}
+
+static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request,
+ void *data_raw)
+{
+ if (!SetRequest_ApiAddScore(request, data_raw))
+ {
+ FreeThreadData_ApiAddScore(data_raw);
+
+ return;
+ }
+
+ emscripten_async_wget2_data(request->uri,
+ request->method,
+ request->body,
+ data_raw,
+ TRUE,
+ Emscripten_ApiAddScore_Loaded,
+ Emscripten_ApiAddScore_Failed,
+ Emscripten_ApiAddScore_Progress);
+}
+
+#else
+
+static void ApiAddScore_HttpRequestExt(struct HttpRequest *request,
+ struct HttpResponse *response,
+ void *data_raw)
+{
+ if (!SetRequest_ApiAddScore(request, data_raw))
+ return;
+
if (!DoHttpRequest(request, response))
{
Error("HTTP request failed: %s", GetHttpError());
return;
}
- server_scores.uploaded = TRUE;
+ HandleResponse_ApiAddScore(response, data_raw);
}
-static void UploadScoreToServer(int level_nr, char *score_tape_filename,
- struct ScoreEntry *score_entry)
+static void ApiAddScore_HttpRequest(struct HttpRequest *request,
+ struct HttpResponse *response,
+ void *data_raw)
{
- struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
- struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+ ApiAddScore_HttpRequestExt(request, response, data_raw);
- UploadScoreToServerExt(request, response,
- level_nr, score_tape_filename, score_entry);
-
- checked_free(request);
- checked_free(response);
+ FreeThreadData_ApiAddScore(data_raw);
}
+#endif
-struct UploadScoreToServerThreadData
-{
- int level_nr;
- char *score_tape_filename;
- struct ScoreEntry score_entry;
-};
-
-static int UploadScoreToServerThread(void *data_raw)
+static int ApiAddScoreThread(void *data_raw)
{
- struct UploadScoreToServerThreadData *data = data_raw;
+ struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
+ struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
- UploadScoreToServer(data->level_nr,
- data->score_tape_filename,
- &data->score_entry);
+#if defined(PLATFORM_EMSCRIPTEN)
+ Emscripten_ApiAddScore_HttpRequest(request, data_raw);
+#else
+ ApiAddScore_HttpRequest(request, response, data_raw);
+#endif
- checked_free(data->score_tape_filename);
- checked_free(data);
+ checked_free(request);
+ checked_free(response);
return 0;
}
-static void UploadScoreToServerAsThread(int nr, char *score_tape_filename)
+static void ApiAddScoreAsThread(int nr, boolean tape_saved,
+ char *score_tape_filename)
{
- struct UploadScoreToServerThreadData *data =
- checked_malloc(sizeof(struct UploadScoreToServerThreadData));
- struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
-
- if (score_tape_filename == NULL)
- score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
+ struct ApiAddScoreThreadData *data =
+ CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename);
- data->level_nr = nr;
- data->score_entry = *score_entry;
- data->score_tape_filename = getStringCopy(score_tape_filename);
-
- ExecuteAsThread(UploadScoreToServerThread,
- "UploadScoreToServer", data,
+ ExecuteAsThread(ApiAddScoreThread,
+ "ApiAddScore", data,
"upload score to server");
}
-void SaveServerScore(int nr)
+void SaveServerScore(int nr, boolean tape_saved)
{
if (!runtime.use_api_server)
return;
- UploadScoreToServerAsThread(nr, NULL);
+ ApiAddScoreAsThread(nr, tape_saved, NULL);
}
-void SaveServerScoreFromFile(int nr, char *score_tape_filename)
+void SaveServerScoreFromFile(int nr, boolean tape_saved,
+ char *score_tape_filename)
{
if (!runtime.use_api_server)
return;
- UploadScoreToServerAsThread(nr, score_tape_filename);
+ ApiAddScoreAsThread(nr, tape_saved, score_tape_filename);
}
void LoadLocalAndServerScore(int nr, boolean download_score)
TYPE_STRING,
&setup.player_name, "player_name"
},
- {
- TYPE_STRING,
- &setup.player_uuid, "player_uuid"
- },
{
TYPE_SWITCH,
&setup.multiple_users, "multiple_users"
TYPE_STRING,
&setup.network_server_hostname, "network_server_hostname"
},
- {
- TYPE_SWITCH,
- &setup.use_api_server, TEST_PREFIX "use_api_server"
- },
- {
- TYPE_STRING,
- &setup.api_server_hostname, TEST_PREFIX "api_server_hostname"
- },
- {
- TYPE_STRING,
- &setup.api_server_password, TEST_PREFIX "api_server_password"
- },
- {
- TYPE_SWITCH,
- &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes"
- },
- {
- TYPE_SWITCH,
- &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes"
- },
{
TYPE_STRING,
&setup.touch.control_type, "touch.control_type"
},
};
+static struct TokenInfo server_setup_tokens[] =
+{
+ {
+ TYPE_STRING,
+ &setup.player_uuid, "player_uuid"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.use_api_server, TEST_PREFIX "use_api_server"
+ },
+ {
+ TYPE_STRING,
+ &setup.api_server_hostname, TEST_PREFIX "api_server_hostname"
+ },
+ {
+ TYPE_STRING,
+ &setup.api_server_password, TEST_PREFIX "api_server_password"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes"
+ },
+};
+
static struct TokenInfo editor_setup_tokens[] =
{
{
int i;
si->player_name = getStringCopy(getDefaultUserName(user.nr));
- si->player_uuid = NULL; // (will be set later)
si->multiple_users = TRUE;
si->network_player_nr = 0; // first player
si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT);
- si->use_api_server = TRUE;
- si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME);
- si->api_server_password = getStringCopy(UNDEFINED_PASSWORD);
- si->ask_for_uploading_tapes = TRUE;
- si->provide_uploading_tapes = TRUE;
-
si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT);
si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT; // percent
si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT; // percent
si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
}
+static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si)
+{
+ si->player_uuid = NULL; // (will be set later)
+
+ si->use_api_server = TRUE;
+ si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME);
+ si->api_server_password = getStringCopy(UNDEFINED_PASSWORD);
+ si->ask_for_uploading_tapes = TRUE;
+ si->provide_uploading_tapes = TRUE;
+}
+
static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
{
si->editor_cascade.el_bd = TRUE;
token_info[token_nr].text);
}
-static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash)
{
int i, pnr;
auto_setup_tokens[i].text));
}
+static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash)
+{
+ int i;
+
+ if (!setup_file_hash)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+ setSetupInfo(server_setup_tokens, i,
+ getHashEntry(setup_file_hash,
+ server_setup_tokens[i].text));
+}
+
static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
{
int i;
if (setup_file_hash)
{
- decodeSetupFileHash(setup_file_hash);
+ decodeSetupFileHash_Default(setup_file_hash);
freeSetupFileHash(setup_file_hash);
}
// make sure that scroll delay value stays inside valid range
setup.scroll_delay_value =
MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
-
- if (setup.player_uuid == NULL)
- {
- // player UUID does not yet exist in setup file
- setup.player_uuid = getStringCopy(getUUID());
-
- SaveSetup();
- }
}
-void LoadSetup(void)
+void LoadSetup_Default(void)
{
char *filename;
free(filename);
}
+void LoadSetup_ServerSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+ SetupFileHash *setup_file_hash = NULL;
+
+ // always start with reliable default values
+ setSetupInfoToDefaults_ServerSetup(&setup);
+
+ setup_file_hash = loadSetupFileHash(filename);
+
+ if (setup_file_hash)
+ {
+ decodeSetupFileHash_ServerSetup(setup_file_hash);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+
+ free(filename);
+
+ if (setup.player_uuid == NULL)
+ {
+ // player UUID does not yet exist in setup file
+ setup.player_uuid = getStringCopy(getUUID());
+
+ SaveSetup_ServerSetup();
+ }
+}
+
void LoadSetup_EditorCascade(void)
{
char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
free(filename);
}
+void LoadSetup(void)
+{
+ LoadSetup_Default();
+ LoadSetup_AutoSetup();
+ LoadSetup_ServerSetup();
+ LoadSetup_EditorCascade();
+}
+
static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
char *mapping_line)
{
fclose(file);
}
-void SaveSetup(void)
+void SaveSetup_Default(void)
{
char *filename = getSetupFilename();
FILE *file;
global_setup_tokens[i].value == &setup.graphics_set ||
global_setup_tokens[i].value == &setup.volume_simple ||
global_setup_tokens[i].value == &setup.network_mode ||
- global_setup_tokens[i].value == &setup.use_api_server ||
global_setup_tokens[i].value == &setup.touch.control_type ||
global_setup_tokens[i].value == &setup.touch.grid_xsize[0] ||
global_setup_tokens[i].value == &setup.touch.grid_xsize[1])
free(filename);
}
+void SaveSetup_ServerSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+ FILE *file;
+ int i;
+
+ InitUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot write server setup file '%s'", filename);
+
+ free(filename);
+
+ return;
+ }
+
+ fprintFileHeader(file, SERVERSETUP_FILENAME);
+
+ for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+ {
+ // just to make things nicer :)
+ if (server_setup_tokens[i].value == &setup.use_api_server)
+ fprintf(file, "\n");
+
+ fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i));
+ }
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+
+ free(filename);
+}
+
void SaveSetup_EditorCascade(void)
{
char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
free(filename);
}
+void SaveSetup(void)
+{
+ SaveSetup_Default();
+ SaveSetup_AutoSetup();
+ SaveSetup_ServerSetup();
+ SaveSetup_EditorCascade();
+}
+
static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
char *filename)
{