moved API functions to separate source file
[rocksndiamonds.git] / src / api.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2022 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // api.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "api.h"
15 #include "main.h"
16 #include "files.h"
17 #include "config.h"
18
19
20 // ============================================================================
21 // generic helper functions
22 // ============================================================================
23
24 static void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data,
25                             char *error)
26 {
27 #if defined(PLATFORM_EMSCRIPTEN)
28   // threads currently not fully supported by Emscripten/SDL and some browsers
29   function(data);
30 #else
31   SDL_Thread *thread = SDL_CreateThread(function, name, data);
32
33   if (thread != NULL)
34     SDL_DetachThread(thread);
35   else
36     Error("Cannot create thread to %s!", error);
37
38   // nasty kludge to lower probability of intermingled thread error messages
39   Delay(1);
40 #endif
41 }
42
43 static char *getPasswordJSON(char *password)
44 {
45   static char password_json[MAX_FILENAME_LEN] = "";
46   static boolean initialized = FALSE;
47
48   if (!initialized)
49   {
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);
56
57     initialized = TRUE;
58   }
59
60   return password_json;
61 }
62
63 static char *getFileBase64(char *filename)
64 {
65   struct stat file_status;
66
67   if (stat(filename, &file_status) != 0)
68   {
69     Error("cannot stat file '%s'", filename);
70
71     return NULL;
72   }
73
74   int buffer_size = file_status.st_size;
75   byte *buffer = checked_malloc(buffer_size);
76   FILE *file;
77   int i;
78
79   if (!(file = fopen(filename, MODE_READ)))
80   {
81     Error("cannot open file '%s'", filename);
82
83     checked_free(buffer);
84
85     return NULL;
86   }
87
88   for (i = 0; i < buffer_size; i++)
89   {
90     int c = fgetc(file);
91
92     if (c == EOF)
93     {
94       Error("cannot read from input file '%s'", filename);
95
96       fclose(file);
97       checked_free(buffer);
98
99       return NULL;
100     }
101
102     buffer[i] = (byte)c;
103   }
104
105   fclose(file);
106
107   int buffer_encoded_size = base64_encoded_size(buffer_size);
108   char *buffer_encoded = checked_malloc(buffer_encoded_size);
109
110   base64_encode(buffer_encoded, buffer, buffer_size);
111
112   checked_free(buffer);
113
114   return buffer_encoded;
115 }
116
117
118 // ============================================================================
119 // add score API functions
120 // ============================================================================
121
122 struct ApiAddScoreThreadData
123 {
124   int level_nr;
125   boolean tape_saved;
126   char *leveldir_subdir;
127   char *score_tape_filename;
128   struct ScoreEntry score_entry;
129 };
130
131 static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved,
132                                           char *score_tape_filename)
133 {
134   struct ApiAddScoreThreadData *data =
135     checked_malloc(sizeof(struct ApiAddScoreThreadData));
136   struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
137
138   if (score_tape_filename == NULL)
139     score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
140
141   data->level_nr = 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;
146
147   return data;
148 }
149
150 static void FreeThreadData_ApiAddScore(void *data_raw)
151 {
152   struct ApiAddScoreThreadData *data = data_raw;
153
154   checked_free(data->leveldir_subdir);
155   checked_free(data->score_tape_filename);
156   checked_free(data);
157 }
158
159 static boolean SetRequest_ApiAddScore(struct HttpRequest *request,
160                                       void *data_raw)
161 {
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;
167
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;
172
173   char *tape_base64 = getFileBase64(score_tape_filename);
174
175   if (tape_base64 == NULL)
176   {
177     Error("loading and base64 encoding score tape file failed");
178
179     return FALSE;
180   }
181
182   char *player_name_raw = score_entry->name;
183   char *player_uuid_raw = setup.player_uuid;
184
185   if (options.player_name != NULL && global.autoplay_leveldir != NULL)
186   {
187     player_name_raw = options.player_name;
188     player_uuid_raw = "";
189   }
190
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);
198
199   snprintf(request->body, MAX_HTTP_BODY_SIZE,
200            "{\n"
201            "%s"
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"
222            "}\n",
223            getPasswordJSON(setup.api_server_password),
224            getProgramRealVersionString(),
225            getProgramPlatformString(),
226            (int)global.autoplay_time,
227            levelset_identifier,
228            levelset_name,
229            levelset_author,
230            leveldir_current->levels,
231            leveldir_current->first_level,
232            level_nr,
233            level_name,
234            level_author,
235            level.use_step_counter,
236            level.rate_time_over_score,
237            player_name,
238            player_uuid,
239            score_entry->score,
240            score_entry->time,
241            score_entry->tape_basename,
242            tape_saved,
243            tape_base64);
244
245   checked_free(tape_base64);
246
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);
254
255   ConvertHttpRequestBodyToServerEncoding(request);
256
257   return TRUE;
258 }
259
260 static void HandleResponse_ApiAddScore(struct HttpResponse *response,
261                                        void *data_raw)
262 {
263   server_scores.uploaded = TRUE;
264 }
265
266 static void HandleFailure_ApiAddScore(void *data_raw)
267 {
268   struct ApiAddScoreThreadData *data = data_raw;
269
270   PrepareScoreTapesForUpload(data->leveldir_subdir);
271 }
272
273 #if defined(PLATFORM_EMSCRIPTEN)
274 static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw,
275                                           void *buffer, unsigned int size)
276 {
277   struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
278
279   if (response != NULL)
280   {
281     HandleResponse_ApiAddScore(response, data_raw);
282
283     checked_free(response);
284   }
285   else
286   {
287     Error("server response too large to handle (%d bytes)", size);
288
289     HandleFailure_ApiAddScore(data_raw);
290   }
291
292   FreeThreadData_ApiAddScore(data_raw);
293 }
294
295 static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw,
296                                           int code, const char *status)
297 {
298   Error("server failed to handle request: %d %s", code, status);
299
300   HandleFailure_ApiAddScore(data_raw);
301
302   FreeThreadData_ApiAddScore(data_raw);
303 }
304
305 static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw,
306                                             int bytes, int size)
307 {
308   // nothing to do here
309 }
310
311 static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request,
312                                                void *data_raw)
313 {
314   if (!SetRequest_ApiAddScore(request, data_raw))
315   {
316     FreeThreadData_ApiAddScore(data_raw);
317
318     return;
319   }
320
321   emscripten_async_wget2_data(request->uri,
322                               request->method,
323                               request->body,
324                               data_raw,
325                               TRUE,
326                               Emscripten_ApiAddScore_Loaded,
327                               Emscripten_ApiAddScore_Failed,
328                               Emscripten_ApiAddScore_Progress);
329 }
330
331 #else
332
333 static void ApiAddScore_HttpRequestExt(struct HttpRequest *request,
334                                        struct HttpResponse *response,
335                                        void *data_raw)
336 {
337   if (!SetRequest_ApiAddScore(request, data_raw))
338     return;
339
340   if (!DoHttpRequest(request, response))
341   {
342     Error("HTTP request failed: %s", GetHttpError());
343
344     HandleFailure_ApiAddScore(data_raw);
345
346     return;
347   }
348
349   if (!HTTP_SUCCESS(response->status_code))
350   {
351     Error("server failed to handle request: %d %s",
352           response->status_code,
353           response->status_text);
354
355     HandleFailure_ApiAddScore(data_raw);
356
357     return;
358   }
359
360   HandleResponse_ApiAddScore(response, data_raw);
361 }
362
363 static void ApiAddScore_HttpRequest(struct HttpRequest *request,
364                                     struct HttpResponse *response,
365                                     void *data_raw)
366 {
367   ApiAddScore_HttpRequestExt(request, response, data_raw);
368
369   FreeThreadData_ApiAddScore(data_raw);
370 }
371 #endif
372
373 static int ApiAddScoreThread(void *data_raw)
374 {
375   struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
376   struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
377
378   program.api_thread_count++;
379
380 #if defined(PLATFORM_EMSCRIPTEN)
381   Emscripten_ApiAddScore_HttpRequest(request, data_raw);
382 #else
383   ApiAddScore_HttpRequest(request, response, data_raw);
384 #endif
385
386   program.api_thread_count--;
387
388   checked_free(request);
389   checked_free(response);
390
391   return 0;
392 }
393
394 void ApiAddScoreAsThread(int nr, boolean tape_saved, char *score_tape_filename)
395 {
396   struct ApiAddScoreThreadData *data =
397     CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename);
398
399   ExecuteAsThread(ApiAddScoreThread,
400                   "ApiAddScore", data,
401                   "upload score to server");
402 }
403
404
405 // ============================================================================
406 // get score API functions
407 // ============================================================================
408
409 struct ApiGetScoreThreadData
410 {
411   int level_nr;
412   char *score_cache_filename;
413 };
414
415 static void *CreateThreadData_ApiGetScore(int nr)
416 {
417   struct ApiGetScoreThreadData *data =
418     checked_malloc(sizeof(struct ApiGetScoreThreadData));
419   char *score_cache_filename = getScoreCacheFilename(nr);
420
421   data->level_nr = nr;
422   data->score_cache_filename = getStringCopy(score_cache_filename);
423
424   return data;
425 }
426
427 static void FreeThreadData_ApiGetScore(void *data_raw)
428 {
429   struct ApiGetScoreThreadData *data = data_raw;
430
431   checked_free(data->score_cache_filename);
432   checked_free(data);
433 }
434
435 static boolean SetRequest_ApiGetScore(struct HttpRequest *request,
436                                       void *data_raw)
437 {
438   struct ApiGetScoreThreadData *data = data_raw;
439   int level_nr = data->level_nr;
440
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;
445
446   char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
447   char *levelset_name       = getEscapedJSON(leveldir_current->name);
448
449   snprintf(request->body, MAX_HTTP_BODY_SIZE,
450            "{\n"
451            "%s"
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"
457            "}\n",
458            getPasswordJSON(setup.api_server_password),
459            getProgramRealVersionString(),
460            getProgramPlatformString(),
461            levelset_identifier,
462            levelset_name,
463            level_nr);
464
465   checked_free(levelset_identifier);
466   checked_free(levelset_name);
467
468   ConvertHttpRequestBodyToServerEncoding(request);
469
470   return TRUE;
471 }
472
473 static void HandleResponse_ApiGetScore(struct HttpResponse *response,
474                                        void *data_raw)
475 {
476   struct ApiGetScoreThreadData *data = data_raw;
477
478   if (response->body_size == 0)
479   {
480     // no scores available for this level
481
482     return;
483   }
484
485   ConvertHttpResponseBodyToClientEncoding(response);
486
487   char *filename = data->score_cache_filename;
488   FILE *file;
489   int i;
490
491   // used instead of "leveldir_current->subdir" (for network games)
492   InitScoreCacheDirectory(levelset.identifier);
493
494   if (!(file = fopen(filename, MODE_WRITE)))
495   {
496     Warn("cannot save score cache file '%s'", filename);
497
498     return;
499   }
500
501   for (i = 0; i < response->body_size; i++)
502     fputc(response->body[i], file);
503
504   fclose(file);
505
506   SetFilePermissions(filename, PERMS_PRIVATE);
507
508   server_scores.updated = TRUE;
509 }
510
511 #if defined(PLATFORM_EMSCRIPTEN)
512 static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw,
513                                           void *buffer, unsigned int size)
514 {
515   struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
516
517   if (response != NULL)
518   {
519     HandleResponse_ApiGetScore(response, data_raw);
520
521     checked_free(response);
522   }
523   else
524   {
525     Error("server response too large to handle (%d bytes)", size);
526   }
527
528   FreeThreadData_ApiGetScore(data_raw);
529 }
530
531 static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw,
532                                           int code, const char *status)
533 {
534   Error("server failed to handle request: %d %s", code, status);
535
536   FreeThreadData_ApiGetScore(data_raw);
537 }
538
539 static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw,
540                                             int bytes, int size)
541 {
542   // nothing to do here
543 }
544
545 static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request,
546                                                void *data_raw)
547 {
548   if (!SetRequest_ApiGetScore(request, data_raw))
549   {
550     FreeThreadData_ApiGetScore(data_raw);
551
552     return;
553   }
554
555   emscripten_async_wget2_data(request->uri,
556                               request->method,
557                               request->body,
558                               data_raw,
559                               TRUE,
560                               Emscripten_ApiGetScore_Loaded,
561                               Emscripten_ApiGetScore_Failed,
562                               Emscripten_ApiGetScore_Progress);
563 }
564
565 #else
566
567 static void ApiGetScore_HttpRequestExt(struct HttpRequest *request,
568                                        struct HttpResponse *response,
569                                        void *data_raw)
570 {
571   if (!SetRequest_ApiGetScore(request, data_raw))
572     return;
573
574   if (!DoHttpRequest(request, response))
575   {
576     Error("HTTP request failed: %s", GetHttpError());
577
578     return;
579   }
580
581   if (!HTTP_SUCCESS(response->status_code))
582   {
583     // do not show error message if no scores found for this level set
584     if (response->status_code == 404)
585       return;
586
587     Error("server failed to handle request: %d %s",
588           response->status_code,
589           response->status_text);
590
591     return;
592   }
593
594   HandleResponse_ApiGetScore(response, data_raw);
595 }
596
597 static void ApiGetScore_HttpRequest(struct HttpRequest *request,
598                                     struct HttpResponse *response,
599                                     void *data_raw)
600 {
601   ApiGetScore_HttpRequestExt(request, response, data_raw);
602
603   FreeThreadData_ApiGetScore(data_raw);
604 }
605 #endif
606
607 static int ApiGetScoreThread(void *data_raw)
608 {
609   struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
610   struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
611
612   program.api_thread_count++;
613
614 #if defined(PLATFORM_EMSCRIPTEN)
615   Emscripten_ApiGetScore_HttpRequest(request, data_raw);
616 #else
617   ApiGetScore_HttpRequest(request, response, data_raw);
618 #endif
619
620   program.api_thread_count--;
621
622   checked_free(request);
623   checked_free(response);
624
625   return 0;
626 }
627
628 void ApiGetScoreAsThread(int nr)
629 {
630   struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr);
631
632   ExecuteAsThread(ApiGetScoreThread,
633                   "ApiGetScore", data,
634                   "download scores from server");
635 }
636
637
638 // ============================================================================
639 // rename player API functions
640 // ============================================================================
641
642 struct ApiRenamePlayerThreadData
643 {
644   char *player_name;
645   char *player_uuid;
646 };
647
648 static void *CreateThreadData_ApiRenamePlayer(void)
649 {
650   struct ApiRenamePlayerThreadData *data =
651     checked_malloc(sizeof(struct ApiRenamePlayerThreadData));
652
653   data->player_name = getStringCopy(setup.player_name);
654   data->player_uuid = getStringCopy(setup.player_uuid);
655
656   return data;
657 }
658
659 static void FreeThreadData_ApiRenamePlayer(void *data_raw)
660 {
661   struct ApiRenamePlayerThreadData *data = data_raw;
662
663   checked_free(data->player_name);
664   checked_free(data->player_uuid);
665   checked_free(data);
666 }
667
668 static boolean SetRequest_ApiRenamePlayer(struct HttpRequest *request,
669                                           void *data_raw)
670 {
671   struct ApiRenamePlayerThreadData *data = data_raw;
672   char *player_name_raw = data->player_name;
673   char *player_uuid_raw = data->player_uuid;
674
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;
679
680   char *player_name = getEscapedJSON(player_name_raw);
681   char *player_uuid = getEscapedJSON(player_uuid_raw);
682
683   snprintf(request->body, MAX_HTTP_BODY_SIZE,
684            "{\n"
685            "%s"
686            "  \"game_version\":         \"%s\",\n"
687            "  \"game_platform\":        \"%s\",\n"
688            "  \"name\":                 \"%s\",\n"
689            "  \"uuid\":                 \"%s\"\n"
690            "}\n",
691            getPasswordJSON(setup.api_server_password),
692            getProgramRealVersionString(),
693            getProgramPlatformString(),
694            player_name,
695            player_uuid);
696
697   checked_free(player_name);
698   checked_free(player_uuid);
699
700   ConvertHttpRequestBodyToServerEncoding(request);
701
702   return TRUE;
703 }
704
705 static void HandleResponse_ApiRenamePlayer(struct HttpResponse *response,
706                                            void *data_raw)
707 {
708   // nothing to do here
709 }
710
711 #if defined(PLATFORM_EMSCRIPTEN)
712 static void Emscripten_ApiRenamePlayer_Loaded(unsigned handle, void *data_raw,
713                                               void *buffer, unsigned int size)
714 {
715   struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
716
717   if (response != NULL)
718   {
719     HandleResponse_ApiRenamePlayer(response, data_raw);
720
721     checked_free(response);
722   }
723   else
724   {
725     Error("server response too large to handle (%d bytes)", size);
726   }
727
728   FreeThreadData_ApiRenamePlayer(data_raw);
729 }
730
731 static void Emscripten_ApiRenamePlayer_Failed(unsigned handle, void *data_raw,
732                                               int code, const char *status)
733 {
734   Error("server failed to handle request: %d %s", code, status);
735
736   FreeThreadData_ApiRenamePlayer(data_raw);
737 }
738
739 static void Emscripten_ApiRenamePlayer_Progress(unsigned handle, void *data_raw,
740                                                 int bytes, int size)
741 {
742   // nothing to do here
743 }
744
745 static void Emscripten_ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
746                                                    void *data_raw)
747 {
748   if (!SetRequest_ApiRenamePlayer(request, data_raw))
749   {
750     FreeThreadData_ApiRenamePlayer(data_raw);
751
752     return;
753   }
754
755   emscripten_async_wget2_data(request->uri,
756                               request->method,
757                               request->body,
758                               data_raw,
759                               TRUE,
760                               Emscripten_ApiRenamePlayer_Loaded,
761                               Emscripten_ApiRenamePlayer_Failed,
762                               Emscripten_ApiRenamePlayer_Progress);
763 }
764
765 #else
766
767 static void ApiRenamePlayer_HttpRequestExt(struct HttpRequest *request,
768                                            struct HttpResponse *response,
769                                            void *data_raw)
770 {
771   if (!SetRequest_ApiRenamePlayer(request, data_raw))
772     return;
773
774   if (!DoHttpRequest(request, response))
775   {
776     Error("HTTP request failed: %s", GetHttpError());
777
778     return;
779   }
780
781   if (!HTTP_SUCCESS(response->status_code))
782   {
783     Error("server failed to handle request: %d %s",
784           response->status_code,
785           response->status_text);
786
787     return;
788   }
789
790   HandleResponse_ApiRenamePlayer(response, data_raw);
791 }
792
793 static void ApiRenamePlayer_HttpRequest(struct HttpRequest *request,
794                                     struct HttpResponse *response,
795                                     void *data_raw)
796 {
797   ApiRenamePlayer_HttpRequestExt(request, response, data_raw);
798
799   FreeThreadData_ApiRenamePlayer(data_raw);
800 }
801 #endif
802
803 static int ApiRenamePlayerThread(void *data_raw)
804 {
805   struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
806   struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
807
808   program.api_thread_count++;
809
810 #if defined(PLATFORM_EMSCRIPTEN)
811   Emscripten_ApiRenamePlayer_HttpRequest(request, data_raw);
812 #else
813   ApiRenamePlayer_HttpRequest(request, response, data_raw);
814 #endif
815
816   program.api_thread_count--;
817
818   checked_free(request);
819   checked_free(response);
820
821   return 0;
822 }
823
824 void ApiRenamePlayerAsThread(void)
825 {
826   struct ApiRenamePlayerThreadData *data = CreateThreadData_ApiRenamePlayer();
827
828   ExecuteAsThread(ApiRenamePlayerThread,
829                   "ApiRenamePlayer", data,
830                   "rename player on server");
831 }
832
833
834 // ============================================================================
835 // reset player UUID API functions
836 // ============================================================================
837
838 struct ApiResetUUIDThreadData
839 {
840   char *player_name;
841   char *player_uuid_old;
842   char *player_uuid_new;
843 };
844
845 static void *CreateThreadData_ApiResetUUID(char *uuid_new)
846 {
847   struct ApiResetUUIDThreadData *data =
848     checked_malloc(sizeof(struct ApiResetUUIDThreadData));
849
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);
853
854   return data;
855 }
856
857 static void FreeThreadData_ApiResetUUID(void *data_raw)
858 {
859   struct ApiResetUUIDThreadData *data = data_raw;
860
861   checked_free(data->player_name);
862   checked_free(data->player_uuid_old);
863   checked_free(data->player_uuid_new);
864   checked_free(data);
865 }
866
867 static boolean SetRequest_ApiResetUUID(struct HttpRequest *request,
868                                        void *data_raw)
869 {
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;
874
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;
879
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);
883
884   snprintf(request->body, MAX_HTTP_BODY_SIZE,
885            "{\n"
886            "%s"
887            "  \"game_version\":         \"%s\",\n"
888            "  \"game_platform\":        \"%s\",\n"
889            "  \"name\":                 \"%s\",\n"
890            "  \"uuid_old\":             \"%s\",\n"
891            "  \"uuid_new\":             \"%s\"\n"
892            "}\n",
893            getPasswordJSON(setup.api_server_password),
894            getProgramRealVersionString(),
895            getProgramPlatformString(),
896            player_name,
897            player_uuid_old,
898            player_uuid_new);
899
900   checked_free(player_name);
901   checked_free(player_uuid_old);
902   checked_free(player_uuid_new);
903
904   ConvertHttpRequestBodyToServerEncoding(request);
905
906   return TRUE;
907 }
908
909 static void HandleResponse_ApiResetUUID(struct HttpResponse *response,
910                                         void *data_raw)
911 {
912   struct ApiResetUUIDThreadData *data = data_raw;
913
914   // upgrade player UUID in server setup file
915   setup.player_uuid = getStringCopy(data->player_uuid_new);
916   setup.player_version = 2;
917
918   SaveSetup_ServerSetup();
919 }
920
921 #if defined(PLATFORM_EMSCRIPTEN)
922 static void Emscripten_ApiResetUUID_Loaded(unsigned handle, void *data_raw,
923                                            void *buffer, unsigned int size)
924 {
925   struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
926
927   if (response != NULL)
928   {
929     HandleResponse_ApiResetUUID(response, data_raw);
930
931     checked_free(response);
932   }
933   else
934   {
935     Error("server response too large to handle (%d bytes)", size);
936   }
937
938   FreeThreadData_ApiResetUUID(data_raw);
939 }
940
941 static void Emscripten_ApiResetUUID_Failed(unsigned handle, void *data_raw,
942                                            int code, const char *status)
943 {
944   Error("server failed to handle request: %d %s", code, status);
945
946   FreeThreadData_ApiResetUUID(data_raw);
947 }
948
949 static void Emscripten_ApiResetUUID_Progress(unsigned handle, void *data_raw,
950                                              int bytes, int size)
951 {
952   // nothing to do here
953 }
954
955 static void Emscripten_ApiResetUUID_HttpRequest(struct HttpRequest *request,
956                                                 void *data_raw)
957 {
958   if (!SetRequest_ApiResetUUID(request, data_raw))
959   {
960     FreeThreadData_ApiResetUUID(data_raw);
961
962     return;
963   }
964
965   emscripten_async_wget2_data(request->uri,
966                               request->method,
967                               request->body,
968                               data_raw,
969                               TRUE,
970                               Emscripten_ApiResetUUID_Loaded,
971                               Emscripten_ApiResetUUID_Failed,
972                               Emscripten_ApiResetUUID_Progress);
973 }
974
975 #else
976
977 static void ApiResetUUID_HttpRequestExt(struct HttpRequest *request,
978                                         struct HttpResponse *response,
979                                         void *data_raw)
980 {
981   if (!SetRequest_ApiResetUUID(request, data_raw))
982     return;
983
984   if (!DoHttpRequest(request, response))
985   {
986     Error("HTTP request failed: %s", GetHttpError());
987
988     return;
989   }
990
991   if (!HTTP_SUCCESS(response->status_code))
992   {
993     Error("server failed to handle request: %d %s",
994           response->status_code,
995           response->status_text);
996
997     return;
998   }
999
1000   HandleResponse_ApiResetUUID(response, data_raw);
1001 }
1002
1003 static void ApiResetUUID_HttpRequest(struct HttpRequest *request,
1004                                      struct HttpResponse *response,
1005                                      void *data_raw)
1006 {
1007   ApiResetUUID_HttpRequestExt(request, response, data_raw);
1008
1009   FreeThreadData_ApiResetUUID(data_raw);
1010 }
1011 #endif
1012
1013 static int ApiResetUUIDThread(void *data_raw)
1014 {
1015   struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
1016   struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
1017
1018   program.api_thread_count++;
1019
1020 #if defined(PLATFORM_EMSCRIPTEN)
1021   Emscripten_ApiResetUUID_HttpRequest(request, data_raw);
1022 #else
1023   ApiResetUUID_HttpRequest(request, response, data_raw);
1024 #endif
1025
1026   program.api_thread_count--;
1027
1028   checked_free(request);
1029   checked_free(response);
1030
1031   return 0;
1032 }
1033
1034 void ApiResetUUIDAsThread(char *uuid_new)
1035 {
1036   struct ApiResetUUIDThreadData *data = CreateThreadData_ApiResetUUID(uuid_new);
1037
1038   ExecuteAsThread(ApiResetUUIDThread,
1039                   "ApiResetUUID", data,
1040                   "reset UUID on server");
1041 }