1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2022 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
20 // ============================================================================
21 // generic helper functions
22 // ============================================================================
24 static void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data,
27 #if defined(PLATFORM_EMSCRIPTEN)
28 // threads currently not fully supported by Emscripten/SDL and some browsers
31 SDL_Thread *thread = SDL_CreateThread(function, name, data);
34 SDL_DetachThread(thread);
36 Error("Cannot create thread to %s!", error);
38 // nasty kludge to lower probability of intermingled thread error messages
43 static char *getPasswordJSON(char *password)
45 static char password_json[MAX_FILENAME_LEN] = "";
46 static boolean initialized = FALSE;
50 if (password != NULL &&
51 !strEqual(password, "") &&
52 !strEqual(password, UNDEFINED_PASSWORD))
53 snprintf(password_json, MAX_FILENAME_LEN,
54 " \"password\": \"%s\",\n",
55 setup.api_server_password);
63 static char *getFileBase64(char *filename)
65 struct stat file_status;
67 if (stat(filename, &file_status) != 0)
69 Error("cannot stat file '%s'", filename);
74 int buffer_size = file_status.st_size;
75 byte *buffer = checked_malloc(buffer_size);
79 if (!(file = fopen(filename, MODE_READ)))
81 Error("cannot open file '%s'", filename);
88 for (i = 0; i < buffer_size; i++)
94 Error("cannot read from input file '%s'", filename);
107 int buffer_encoded_size = base64_encoded_size(buffer_size);
108 char *buffer_encoded = checked_malloc(buffer_encoded_size);
110 base64_encode(buffer_encoded, buffer, buffer_size);
112 checked_free(buffer);
114 return buffer_encoded;
118 // ============================================================================
119 // add score API functions
120 // ============================================================================
122 struct ApiAddScoreThreadData
126 char *leveldir_subdir;
127 char *score_tape_filename;
128 struct ScoreEntry score_entry;
131 static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved,
132 char *score_tape_filename)
134 struct ApiAddScoreThreadData *data =
135 checked_malloc(sizeof(struct ApiAddScoreThreadData));
136 struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
138 if (score_tape_filename == NULL)
139 score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
142 data->tape_saved = tape_saved;
143 data->leveldir_subdir = getStringCopy(leveldir_current->subdir);
144 data->score_tape_filename = getStringCopy(score_tape_filename);
145 data->score_entry = *score_entry;
150 static void FreeThreadData_ApiAddScore(void *data_raw)
152 struct ApiAddScoreThreadData *data = data_raw;
154 checked_free(data->leveldir_subdir);
155 checked_free(data->score_tape_filename);
159 static boolean SetRequest_ApiAddScore(struct HttpRequest *request,
162 struct ApiAddScoreThreadData *data = data_raw;
163 struct ScoreEntry *score_entry = &data->score_entry;
164 char *score_tape_filename = data->score_tape_filename;
165 boolean tape_saved = data->tape_saved;
166 int level_nr = data->level_nr;
168 request->hostname = setup.api_server_hostname;
169 request->port = API_SERVER_PORT;
170 request->method = API_SERVER_METHOD;
171 request->uri = API_SERVER_URI_ADD;
173 char *tape_base64 = getFileBase64(score_tape_filename);
175 if (tape_base64 == NULL)
177 Error("loading and base64 encoding score tape file failed");
182 char *player_name_raw = score_entry->name;
183 char *player_uuid_raw = setup.player_uuid;
185 if (options.player_name != NULL && global.autoplay_leveldir != NULL)
187 player_name_raw = options.player_name;
188 player_uuid_raw = "";
191 char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
192 char *levelset_name = getEscapedJSON(leveldir_current->name);
193 char *levelset_author = getEscapedJSON(leveldir_current->author);
194 char *level_name = getEscapedJSON(level.name);
195 char *level_author = getEscapedJSON(level.author);
196 char *player_name = getEscapedJSON(player_name_raw);
197 char *player_uuid = getEscapedJSON(player_uuid_raw);
199 snprintf(request->body, MAX_HTTP_BODY_SIZE,
202 " \"game_version\": \"%s\",\n"
203 " \"game_platform\": \"%s\",\n"
204 " \"batch_time\": \"%d\",\n"
205 " \"levelset_identifier\": \"%s\",\n"
206 " \"levelset_name\": \"%s\",\n"
207 " \"levelset_author\": \"%s\",\n"
208 " \"levelset_num_levels\": \"%d\",\n"
209 " \"levelset_first_level\": \"%d\",\n"
210 " \"level_nr\": \"%d\",\n"
211 " \"level_name\": \"%s\",\n"
212 " \"level_author\": \"%s\",\n"
213 " \"use_step_counter\": \"%d\",\n"
214 " \"rate_time_over_score\": \"%d\",\n"
215 " \"player_name\": \"%s\",\n"
216 " \"player_uuid\": \"%s\",\n"
217 " \"score\": \"%d\",\n"
218 " \"time\": \"%d\",\n"
219 " \"tape_basename\": \"%s\",\n"
220 " \"tape_saved\": \"%d\",\n"
221 " \"tape\": \"%s\"\n"
223 getPasswordJSON(setup.api_server_password),
224 getProgramRealVersionString(),
225 getProgramPlatformString(),
226 (int)global.autoplay_time,
230 leveldir_current->levels,
231 leveldir_current->first_level,
235 level.use_step_counter,
236 level.rate_time_over_score,
241 score_entry->tape_basename,
245 checked_free(tape_base64);
247 checked_free(levelset_identifier);
248 checked_free(levelset_name);
249 checked_free(levelset_author);
250 checked_free(level_name);
251 checked_free(level_author);
252 checked_free(player_name);
253 checked_free(player_uuid);
255 ConvertHttpRequestBodyToServerEncoding(request);
260 static void HandleResponse_ApiAddScore(struct HttpResponse *response,
263 server_scores.uploaded = TRUE;
266 static void HandleFailure_ApiAddScore(void *data_raw)
268 struct ApiAddScoreThreadData *data = data_raw;
270 PrepareScoreTapesForUpload(data->leveldir_subdir);
273 #if defined(PLATFORM_EMSCRIPTEN)
274 static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw,
275 void *buffer, unsigned int size)
277 struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
279 if (response != NULL)
281 HandleResponse_ApiAddScore(response, data_raw);
283 checked_free(response);
287 Error("server response too large to handle (%d bytes)", size);
289 HandleFailure_ApiAddScore(data_raw);
292 FreeThreadData_ApiAddScore(data_raw);
295 static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw,
296 int code, const char *status)
298 Error("server failed to handle request: %d %s", code, status);
300 HandleFailure_ApiAddScore(data_raw);
302 FreeThreadData_ApiAddScore(data_raw);
305 static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw,
308 // nothing to do here
311 static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request,
314 if (!SetRequest_ApiAddScore(request, data_raw))
316 FreeThreadData_ApiAddScore(data_raw);
321 emscripten_async_wget2_data(request->uri,
326 Emscripten_ApiAddScore_Loaded,
327 Emscripten_ApiAddScore_Failed,
328 Emscripten_ApiAddScore_Progress);
333 static void ApiAddScore_HttpRequestExt(struct HttpRequest *request,
334 struct HttpResponse *response,
337 if (!SetRequest_ApiAddScore(request, data_raw))
340 if (!DoHttpRequest(request, response))
342 Error("HTTP request failed: %s", GetHttpError());
344 HandleFailure_ApiAddScore(data_raw);
349 if (!HTTP_SUCCESS(response->status_code))
351 Error("server failed to handle request: %d %s",
352 response->status_code,
353 response->status_text);
355 HandleFailure_ApiAddScore(data_raw);
360 HandleResponse_ApiAddScore(response, data_raw);
363 static void ApiAddScore_HttpRequest(struct HttpRequest *request,
364 struct HttpResponse *response,
367 ApiAddScore_HttpRequestExt(request, response, data_raw);
369 FreeThreadData_ApiAddScore(data_raw);
373 static int ApiAddScoreThread(void *data_raw)
375 struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
376 struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
378 program.api_thread_count++;
380 #if defined(PLATFORM_EMSCRIPTEN)
381 Emscripten_ApiAddScore_HttpRequest(request, data_raw);
383 ApiAddScore_HttpRequest(request, response, data_raw);
386 program.api_thread_count--;
388 checked_free(request);
389 checked_free(response);
394 void ApiAddScoreAsThread(int nr, boolean tape_saved, char *score_tape_filename)
396 struct ApiAddScoreThreadData *data =
397 CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename);
399 ExecuteAsThread(ApiAddScoreThread,
401 "upload score to server");
405 // ============================================================================
406 // get score API functions
407 // ============================================================================
409 struct ApiGetScoreThreadData
412 char *score_cache_filename;
415 static void *CreateThreadData_ApiGetScore(int nr)
417 struct ApiGetScoreThreadData *data =
418 checked_malloc(sizeof(struct ApiGetScoreThreadData));
419 char *score_cache_filename = getScoreCacheFilename(nr);
422 data->score_cache_filename = getStringCopy(score_cache_filename);
427 static void FreeThreadData_ApiGetScore(void *data_raw)
429 struct ApiGetScoreThreadData *data = data_raw;
431 checked_free(data->score_cache_filename);
435 static boolean SetRequest_ApiGetScore(struct HttpRequest *request,
438 struct ApiGetScoreThreadData *data = data_raw;
439 int level_nr = data->level_nr;
441 request->hostname = setup.api_server_hostname;
442 request->port = API_SERVER_PORT;
443 request->method = API_SERVER_METHOD;
444 request->uri = API_SERVER_URI_GET;
446 char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
447 char *levelset_name = getEscapedJSON(leveldir_current->name);
449 snprintf(request->body, MAX_HTTP_BODY_SIZE,
452 " \"game_version\": \"%s\",\n"
453 " \"game_platform\": \"%s\",\n"
454 " \"levelset_identifier\": \"%s\",\n"
455 " \"levelset_name\": \"%s\",\n"
456 " \"level_nr\": \"%d\"\n"
458 getPasswordJSON(setup.api_server_password),
459 getProgramRealVersionString(),
460 getProgramPlatformString(),
465 checked_free(levelset_identifier);
466 checked_free(levelset_name);
468 ConvertHttpRequestBodyToServerEncoding(request);
473 static void HandleResponse_ApiGetScore(struct HttpResponse *response,
476 struct ApiGetScoreThreadData *data = data_raw;
478 if (response->body_size == 0)
480 // no scores available for this level
485 ConvertHttpResponseBodyToClientEncoding(response);
487 char *filename = data->score_cache_filename;
491 // used instead of "leveldir_current->subdir" (for network games)
492 InitScoreCacheDirectory(levelset.identifier);
494 if (!(file = fopen(filename, MODE_WRITE)))
496 Warn("cannot save score cache file '%s'", filename);
501 for (i = 0; i < response->body_size; i++)
502 fputc(response->body[i], file);
506 SetFilePermissions(filename, PERMS_PRIVATE);
508 server_scores.updated = TRUE;
511 #if defined(PLATFORM_EMSCRIPTEN)
512 static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw,
513 void *buffer, unsigned int size)
515 struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
517 if (response != NULL)
519 HandleResponse_ApiGetScore(response, data_raw);
521 checked_free(response);
525 Error("server response too large to handle (%d bytes)", size);
528 FreeThreadData_ApiGetScore(data_raw);
531 static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw,
532 int code, const char *status)
534 Error("server failed to handle request: %d %s", code, status);
536 FreeThreadData_ApiGetScore(data_raw);
539 static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw,
542 // nothing to do here
545 static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request,
548 if (!SetRequest_ApiGetScore(request, data_raw))
550 FreeThreadData_ApiGetScore(data_raw);
555 emscripten_async_wget2_data(request->uri,
560 Emscripten_ApiGetScore_Loaded,
561 Emscripten_ApiGetScore_Failed,
562 Emscripten_ApiGetScore_Progress);
567 static void ApiGetScore_HttpRequestExt(struct HttpRequest *request,
568 struct HttpResponse *response,
571 if (!SetRequest_ApiGetScore(request, data_raw))
574 if (!DoHttpRequest(request, response))
576 Error("HTTP request failed: %s", GetHttpError());
581 if (!HTTP_SUCCESS(response->status_code))
583 // do not show error message if no scores found for this level set
584 if (response->status_code == 404)
587 Error("server failed to handle request: %d %s",
588 response->status_code,
589 response->status_text);
594 HandleResponse_ApiGetScore(response, data_raw);
597 static void ApiGetScore_HttpRequest(struct HttpRequest *request,
598 struct HttpResponse *response,
601 ApiGetScore_HttpRequestExt(request, response, data_raw);
603 FreeThreadData_ApiGetScore(data_raw);
607 static int ApiGetScoreThread(void *data_raw)
609 struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
610 struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
612 program.api_thread_count++;
614 #if defined(PLATFORM_EMSCRIPTEN)
615 Emscripten_ApiGetScore_HttpRequest(request, data_raw);
617 ApiGetScore_HttpRequest(request, response, data_raw);
620 program.api_thread_count--;
622 checked_free(request);
623 checked_free(response);
628 void ApiGetScoreAsThread(int nr)
630 struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr);
632 ExecuteAsThread(ApiGetScoreThread,
634 "download scores from server");
638 // ============================================================================
639 // rename player API functions
640 // ============================================================================
642 struct ApiRenamePlayerThreadData
648 static void *CreateThreadData_ApiRenamePlayer(void)
650 struct ApiRenamePlayerThreadData *data =
651 checked_malloc(sizeof(struct ApiRenamePlayerThreadData));
653 data->player_name = getStringCopy(setup.player_name);
654 data->player_uuid = getStringCopy(setup.player_uuid);
659 static void FreeThreadData_ApiRenamePlayer(void *data_raw)
661 struct ApiRenamePlayerThreadData *data = data_raw;
663 checked_free(data->player_name);
664 checked_free(data->player_uuid);
668 static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request,
671 struct ApiRenamePlayerThreadData *data = data_raw;
672 char *player_name_raw = data->player_name;
673 char *player_uuid_raw = data->player_uuid;
675 request->hostname = setup.api_server_hostname;
676 request->port = API_SERVER_PORT;
677 request->method = API_SERVER_METHOD;
678 request->uri = API_SERVER_URI_RENAME;
680 char *player_name = getEscapedJSON(player_name_raw);
681 char *player_uuid = getEscapedJSON(player_uuid_raw);
683 snprintf(request->body, MAX_HTTP_BODY_SIZE,
686 " \"game_version\": \"%s\",\n"
687 " \"game_platform\": \"%s\",\n"
688 " \"name\": \"%s\",\n"
689 " \"uuid\": \"%s\"\n"
691 getPasswordJSON(setup.api_server_password),
692 getProgramRealVersionString(),
693 getProgramPlatformString(),
697 checked_free(player_name);
698 checked_free(player_uuid);
700 ConvertHttpRequestBodyToServerEncoding(request);
705 static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response,
708 // nothing to do here
711 #if defined(PLATFORM_EMSCRIPTEN)
712 static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw,
713 void *buffer, unsigned int size)
715 struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
717 if (response != NULL)
719 HandleResponse_ApiRenamePlayer(response, data_raw);
721 checked_free(response);
725 Error("server response too large to handle (%d bytes)", size);
728 FreeThreadData_ApiRenamePlayer(data_raw);
731 static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw,
732 int code, const char *status)
734 Error("server failed to handle request: %d %s", code, status);
736 FreeThreadData_ApiRenamePlayer(data_raw);
739 static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw,
742 // nothing to do here
745 static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
748 if (!SetRequest_ApiRenamePlayer(request, data_raw))
750 FreeThreadData_ApiRenamePlayer(data_raw);
755 emscripten_async_wget2_data(request->uri,
760 Emscripten_ApiRenamePlayer_Loaded,
761 Emscripten_ApiRenamePlayer_Failed,
762 Emscripten_ApiRenamePlayer_Progress);
767 static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request,
768 struct HttpResponse *response,
771 if (!SetRequest_ApiRenamePlayer(request, data_raw))
774 if (!DoHttpRequest(request, response))
776 Error("HTTP request failed: %s", GetHttpError());
781 if (!HTTP_SUCCESS(response->status_code))
783 Error("server failed to handle request: %d %s",
784 response->status_code,
785 response->status_text);
790 HandleResponse_ApiRenamePlayer(response, data_raw);
793 static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
794 struct HttpResponse *response,
797 ApiRenamePlayer_HttpRequestExt(request, response, data_raw);
799 FreeThreadData_ApiRenamePlayer(data_raw);
803 static int ApiRenamePlayerThread(void *data_raw)
805 struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
806 struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
808 program.api_thread_count++;
810 #if defined(PLATFORM_EMSCRIPTEN)
811 Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw);
813 ApiRenamePlayer_HttpRequest(request, response, data_raw);
816 program.api_thread_count--;
818 checked_free(request);
819 checked_free(response);
824 void ApiRenamePlayerAsThread(void)
826 struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer();
828 ExecuteAsThread(ApiRenamePlayerThread,
829 "ApiRenamePlayer", data,
830 "rename player on server");
834 // ============================================================================
835 // reset player UUID API functions
836 // ============================================================================
838 struct ApiResetUUIDThreadData
841 char *player_uuid_old;
842 char *player_uuid_new;
845 static void *CreateThreadData_ApiResetUUID(char *uuid_new)
847 struct ApiResetUUIDThreadData *data =
848 checked_malloc(sizeof(struct ApiResetUUIDThreadData));
850 data->player_name = getStringCopy(setup.player_name);
851 data->player_uuid_old = getStringCopy(setup.player_uuid);
852 data->player_uuid_new = getStringCopy(uuid_new);
857 static void FreeThreadData_ApiResetUUID(void *data_raw)
859 struct ApiResetUUIDThreadData *data = data_raw;
861 checked_free(data->player_name);
862 checked_free(data->player_uuid_old);
863 checked_free(data->player_uuid_new);
867 static boolean SetRequest_ApiResetUUID(struct HttpRequest *request,
870 struct ApiResetUUIDThreadData *data = data_raw;
871 char *player_name_raw = data->player_name;
872 char *player_uuid_old_raw = data->player_uuid_old;
873 char *player_uuid_new_raw = data->player_uuid_new;
875 request->hostname = setup.api_server_hostname;
876 request->port = API_SERVER_PORT;
877 request->method = API_SERVER_METHOD;
878 request->uri = API_SERVER_URI_RESETUUID;
880 char *player_name = getEscapedJSON(player_name_raw);
881 char *player_uuid_old = getEscapedJSON(player_uuid_old_raw);
882 char *player_uuid_new = getEscapedJSON(player_uuid_new_raw);
884 snprintf(request->body, MAX_HTTP_BODY_SIZE,
887 " \"game_version\": \"%s\",\n"
888 " \"game_platform\": \"%s\",\n"
889 " \"name\": \"%s\",\n"
890 " \"uuid_old\": \"%s\",\n"
891 " \"uuid_new\": \"%s\"\n"
893 getPasswordJSON(setup.api_server_password),
894 getProgramRealVersionString(),
895 getProgramPlatformString(),
900 checked_free(player_name);
901 checked_free(player_uuid_old);
902 checked_free(player_uuid_new);
904 ConvertHttpRequestBodyToServerEncoding(request);
909 static void HandleResponse_ApiResetUUID(struct HttpResponse *response,
912 struct ApiResetUUIDThreadData *data = data_raw;
914 // upgrade player UUID in server setup file
915 setup.player_uuid = getStringCopy(data->player_uuid_new);
916 setup.player_version = 2;
918 SaveSetup_ServerSetup();
921 #if defined(PLATFORM_EMSCRIPTEN)
922 static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw,
923 void *buffer, unsigned int size)
925 struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
927 if (response != NULL)
929 HandleResponse_ApiResetUUID(response, data_raw);
931 checked_free(response);
935 Error("server response too large to handle (%d bytes)", size);
938 FreeThreadData_ApiResetUUID(data_raw);
941 static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw,
942 int code, const char *status)
944 Error("server failed to handle request: %d %s", code, status);
946 FreeThreadData_ApiResetUUID(data_raw);
949 static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw,
952 // nothing to do here
955 static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request,
958 if (!SetRequest_ApiResetUUID(request, data_raw))
960 FreeThreadData_ApiResetUUID(data_raw);
965 emscripten_async_wget2_data(request->uri,
970 Emscripten_ApiResetUUID_Loaded,
971 Emscripten_ApiResetUUID_Failed,
972 Emscripten_ApiResetUUID_Progress);
977 static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request,
978 struct HttpResponse *response,
981 if (!SetRequest_ApiResetUUID(request, data_raw))
984 if (!DoHttpRequest(request, response))
986 Error("HTTP request failed: %s", GetHttpError());
991 if (!HTTP_SUCCESS(response->status_code))
993 Error("server failed to handle request: %d %s",
994 response->status_code,
995 response->status_text);
1000 HandleResponse_ApiResetUUID(response, data_raw);
1003 static void ApiResetUUID_HttpRequest(struct HttpRequest *request,
1004 struct HttpResponse *response,
1007 ApiResetUUID_HttpRequestExt(request, response, data_raw);
1009 FreeThreadData_ApiResetUUID(data_raw);
1013 static int ApiResetUUIDThread(void *data_raw)
1015 struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
1016 struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
1018 program.api_thread_count++;
1020 #if defined(PLATFORM_EMSCRIPTEN)
1021 Emscripten_ApiResetUUID_HttpRequest(request, data_raw);
1023 ApiResetUUID_HttpRequest(request, response, data_raw);
1026 program.api_thread_count--;
1028 checked_free(request);
1029 checked_free(response);
1034 void ApiResetUUIDAsThread(char *uuid_new)
1036 struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new);
1038 ExecuteAsThread(ApiResetUUIDThread,
1039 "ApiResetUUID", data,
1040 "reset UUID on server");