Subversion Repositories Games.Carmageddon

Rev

Rev 18 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. #include "loadsave.h"
  2. #include "cutscene.h"
  3. #include "errors.h"
  4. #include "flicplay.h"
  5. #include "globvars.h"
  6. #include "globvrpb.h"
  7. #include "grafdata.h"
  8. #include "graphics.h"
  9. #include "harness/config.h"
  10. #include "harness/trace.h"
  11. #include "init.h"
  12. #include "input.h"
  13. #include "intrface.h"
  14. #include "loading.h"
  15. #include "pd/sys.h"
  16. #include "sound.h"
  17. #include "structur.h"
  18. #include "utility.h"
  19. #include "world.h"
  20. #include <brender/brender.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23.  
  24. tSave_game* gSaved_games[8];
  25. int gStarted_typing;
  26. int gSave_allowed;
  27.  
  28. #define SAVEGAME_VERSION 6
  29.  
  30. #define SWAP32_BE(V)       \
  31.     do {                   \
  32.         (V) = BrHtoNL(V); \
  33.     } while (0)
  34.  
  35. // IDA: void __usercall CorrectLoadByteOrdering(int pIndex@<EAX>)
  36. void CorrectLoadByteOrdering(int pIndex) {
  37.     int i;
  38.     LOG_TRACE("(%d)", pIndex);
  39.  
  40.     SWAP32_BE(gSaved_games[pIndex]->version);
  41.     SWAP32_BE(gSaved_games[pIndex]->rank);
  42.     SWAP32_BE(gSaved_games[pIndex]->credits);
  43.     SWAP32_BE(gSaved_games[pIndex]->skill_level);
  44.     SWAP32_BE(gSaved_games[pIndex]->frank_or_annitude);
  45.     SWAP32_BE(gSaved_games[pIndex]->game_completed);
  46.     SWAP32_BE(gSaved_games[pIndex]->current_race_index);
  47.     SWAP32_BE(gSaved_games[pIndex]->number_of_cars);
  48.     for (i = 0; i < COUNT_OF(gSaved_games[pIndex]->cars_available); i++) {
  49.         SWAP32_BE(gSaved_games[pIndex]->cars_available[i]);
  50.     }
  51.     SWAP32_BE(gSaved_games[pIndex]->current_car_index);
  52.     SWAP32_BE(gSaved_games[pIndex]->redo_race_index);
  53.     for (i = 0; i < COUNT_OF(gSaved_games[pIndex]->power_up_levels); i++) {
  54.         SWAP32_BE(gSaved_games[pIndex]->power_up_levels[i]);
  55.     }
  56. }
  57.  
  58. // IDA: tU32 __usercall CalcLSChecksum@<EAX>(tSave_game *pSaved_game@<EAX>)
  59. tU32 CalcLSChecksum(tSave_game* pSaved_game) {
  60.     tU32 checksum;
  61.     tU32 checksum2;
  62.     int i;
  63.     tU8* ptr;
  64.     LOG_TRACE("(%p)", pSaved_game);
  65.  
  66. #ifdef DETHRACE_FIX_BUGS
  67.     if (sizeof(tSave_game) - sizeof(tU32) != offsetof(tSave_game, checksum)) {
  68.         PDFatalError("checksum must be last element of savegame struct");
  69.     }
  70. #endif
  71.  
  72.     checksum = 0;
  73.     for (i = 0, ptr = (tU8*)pSaved_game; i < (sizeof(tSave_game) - sizeof(tU32)); i++, ptr++) {
  74.         checksum2 = (*ptr ^ 0xbd) + checksum;
  75.         checksum = checksum ^ checksum2 << 25 ^ checksum2 >> 7;
  76.     }
  77.     return checksum;
  78. }
  79.  
  80. // IDA: void __cdecl LoadSavedGames()
  81. void LoadSavedGames(void) {
  82.     tPath_name the_path;
  83.     int i;
  84.     FILE* f;
  85.     tU32 the_size;
  86.     LOG_TRACE("()");
  87.  
  88. #ifdef DETHRACE_FIX_BUGS
  89.     if (sizeof(tSave_game) != 948) {
  90.         PDFatalError("Size of tSave_game struct is not correct (should be 948 bytes)");
  91.     }
  92. #endif
  93.  
  94.     PathCat(the_path, gApplication_path, "SAVEGAME");
  95.     PathCat(the_path, the_path, "SAVEx");
  96.     for (i = 0; i < COUNT_OF(gSaved_games); i++) {
  97.         the_path[strlen(the_path) - 1] = '0' + i;
  98.         f = DRfopen(the_path, "rb");
  99.         if (f == NULL) {
  100.             continue;
  101.         }
  102.         the_size = GetFileLength(f);
  103.         if (the_size == sizeof(tSave_game)) {
  104.             gSaved_games[i] = BrMemCalloc(1, sizeof(tSave_game), kMem_saved_game);
  105.             fread(gSaved_games[i], 1, the_size, f);
  106.             CorrectLoadByteOrdering(i);
  107.             if (CalcLSChecksum(gSaved_games[i]) != gSaved_games[i]->checksum || gSaved_games[i]->version != SAVEGAME_VERSION) {
  108.                 BrMemFree(gSaved_games[i]);
  109.                 gSaved_games[i] = NULL;
  110.             }
  111.         } else {
  112.             gSaved_games[i] = NULL;
  113.         }
  114.         fclose(f);
  115.     }
  116. }
  117.  
  118. // IDA: void __cdecl DisposeSavedGames()
  119. void DisposeSavedGames(void) {
  120.     int i;
  121.     LOG_TRACE("()");
  122.  
  123.     for (i = 0; i < COUNT_OF(gSaved_games); i++) {
  124.         if (gSaved_games[i] != NULL) {
  125.             BrMemFree(gSaved_games[i]);
  126.         }
  127.     }
  128. }
  129.  
  130. // IDA: void __usercall LoadTheGame(int pSlot_index@<EAX>)
  131. void LoadTheGame(int pSlot_index) {
  132.     int i;
  133.     //char the_car_name[14]; // Pierre-Marie Baty -- unused variable
  134.     LOG_TRACE("(%d)", pSlot_index);
  135.  
  136.     if (gProgram_state.car_name[0] != '\0') {
  137.         DisposeCar(&gProgram_state.current_car, gProgram_state.current_car.index);
  138.         ClearOutStorageSpace(&gOur_car_storage_space);
  139.     }
  140.     strcpy(gProgram_state.player_name[0], gSaved_games[pSlot_index]->player_name[0]);
  141.     strcpy(gProgram_state.player_name[1], gSaved_games[pSlot_index]->player_name[1]);
  142.     gProgram_state.current_car_index = gSaved_games[pSlot_index]->current_car_index;
  143.     gProgram_state.redo_race_index = gSaved_games[pSlot_index]->redo_race_index;
  144.     gProgram_state.frank_or_anniness = gSaved_games[pSlot_index]->frank_or_annitude;
  145.     gProgram_state.car_name[0] = '\0';
  146.     AboutToLoadFirstCar();
  147.     SwitchToRealResolution();
  148.     LoadCar(gSaved_games[pSlot_index]->car_name, eDriver_local_human,
  149.         &gProgram_state.current_car, gProgram_state.current_car_index,
  150.         gProgram_state.player_name[gProgram_state.frank_or_anniness],
  151.         &gOur_car_storage_space);
  152.     SwitchToLoresMode();
  153.     SetCarStorageTexturingLevel(&gOur_car_storage_space, GetCarTexturingLevel(), eCTL_full);
  154.     gProgram_state.skill_level = gSaved_games[pSlot_index]->skill_level;
  155.     gProgram_state.current_race_index = gSaved_games[pSlot_index]->current_race_index;
  156.     InitGame(gProgram_state.current_race_index);
  157.     gProgram_state.number_of_cars = gSaved_games[pSlot_index]->number_of_cars;
  158.     memcpy(gProgram_state.cars_available, gSaved_games[pSlot_index]->cars_available, sizeof(gProgram_state.cars_available));
  159.     for (i = 0; i < gNumber_of_races; i++) {
  160.         gRace_list[i].been_there_done_that = gSaved_games[pSlot_index]->race_info[i].been_there_done_that;
  161.     }
  162.     for (i = 0; i < gNumber_of_racers; i++) {
  163.         gOpponents[i].dead = gSaved_games[pSlot_index]->opponent_info[i].dead;
  164.     }
  165.     gProgram_state.credits = gSaved_games[pSlot_index]->credits;
  166.     gProgram_state.rank = gSaved_games[pSlot_index]->rank;
  167.     for (i = 0; i < COUNT_OF(gProgram_state.current_car.power_up_levels); i++) {
  168.         gProgram_state.current_car.power_up_levels[i] = gSaved_games[pSlot_index]->power_up_levels[i];
  169.     }
  170.     gProgram_state.game_completed = gSaved_games[pSlot_index]->game_completed;
  171.     SetSoundVolumes();
  172.     gProgram_state.saving = 0;
  173.     SelectOpponents(&gCurrent_race);
  174. }
  175.  
  176. // IDA: void __cdecl StartRollingSaveNamesIn()
  177. void StartRollingSaveNamesIn(void) {
  178.     int i;
  179.     int save_slot_height;
  180.     LOG_TRACE("()");
  181.  
  182.     for (i = 0; i < COUNT_OF(gSaved_games); i++) {
  183.         save_slot_height = gCurrent_graf_data->save_slot_y_offset + i * gCurrent_graf_data->rolling_letter_y_pitch;
  184.         SetSlotXY(i, gCurrent_graf_data->save_slot_x_offset, save_slot_height);
  185.         if (gSaved_games[i] != NULL) {
  186.             AddRollingString(gSaved_games[i]->slot_name, gCurrent_graf_data->save_slot_x_offset, save_slot_height, eRT_alpha);
  187.             AddRollingNumber(gSaved_games[i]->rank, 2, gCurrent_graf_data->save_slot_rank_x_offset, save_slot_height);
  188.             AddRollingNumber(gSaved_games[i]->credits, 6, gCurrent_graf_data->save_slot_credits_x_offset, save_slot_height);
  189.         } else {
  190.             AddRollingString(GetMiscString(kMiscString_Empty), gCurrent_graf_data->save_slot_x_offset, save_slot_height, eRT_alpha);
  191.         }
  192.     }
  193. }
  194.  
  195. // IDA: void __cdecl LoadStart()
  196. void LoadStart(void) {
  197.     LOG_TRACE("()");
  198.  
  199.     StartRollingSaveNamesIn();
  200. }
  201.  
  202. // IDA: int __usercall DoLoadGame@<EAX>(int pSave_allowed@<EAX>)
  203. int DoLoadGame(void) {
  204.     static tFlicette flicker_on[9] = {
  205.         { 74, { 47, 94 }, { 23, 55 } },
  206.         { 74, { 47, 94 }, { 44, 110 } },
  207.         { 74, { 47, 94 }, { 65, 156 } },
  208.         { 74, { 47, 94 }, { 86, 206 } },
  209.         { 74, { 47, 94 }, { 107, 257 } },
  210.         { 74, { 47, 94 }, { 128, 307 } },
  211.         { 74, { 47, 94 }, { 149, 358 } },
  212.         { 74, { 47, 94 }, { 170, 408 } },
  213.         { 57, { 255, 510 }, { 151, 362 } },
  214.     };
  215.     static tFlicette flicker_off[9] = {
  216.         { 73, { 47, 94 }, { 23, 55 } },
  217.         { 73, { 47, 94 }, { 44, 110 } },
  218.         { 73, { 47, 94 }, { 65, 156 } },
  219.         { 73, { 47, 94 }, { 86, 206 } },
  220.         { 73, { 47, 94 }, { 107, 257 } },
  221.         { 73, { 47, 94 }, { 128, 307 } },
  222.         { 73, { 47, 94 }, { 149, 358 } },
  223.         { 73, { 47, 94 }, { 170, 408 } },
  224.         { 56, { 255, 510 }, { 151, 362 } },
  225.     };
  226.     static tFlicette push[9] = {
  227.         { 74, { 47, 94 }, { 23, 55 } },
  228.         { 74, { 47, 94 }, { 44, 110 } },
  229.         { 74, { 47, 94 }, { 65, 156 } },
  230.         { 74, { 47, 94 }, { 86, 206 } },
  231.         { 74, { 47, 94 }, { 107, 257 } },
  232.         { 74, { 47, 94 }, { 128, 307 } },
  233.         { 74, { 47, 94 }, { 149, 358 } },
  234.         { 74, { 47, 94 }, { 170, 408 } },
  235.         { 59, { 255, 510 }, { 151, 362 } },
  236.     };
  237.     static tMouse_area mouse_areas[9] = {
  238.         { { 48, 96 }, { 17, 41 }, { 250, 500 }, { 33, 79 }, 0, 0, 0, NULL },
  239.         { { 48, 96 }, { 39, 94 }, { 250, 500 }, { 55, 132 }, 1, 0, 0, NULL },
  240.         { { 48, 96 }, { 59, 142 }, { 250, 500 }, { 75, 180 }, 2, 0, 0, NULL },
  241.         { { 48, 96 }, { 80, 192 }, { 250, 500 }, { 96, 230 }, 3, 0, 0, NULL },
  242.         { { 48, 96 }, { 101, 242 }, { 250, 500 }, { 117, 281 }, 4, 0, 0, NULL },
  243.         { { 48, 96 }, { 122, 293 }, { 250, 500 }, { 138, 331 }, 5, 0, 0, NULL },
  244.         { { 48, 96 }, { 143, 343 }, { 250, 500 }, { 159, 382 }, 6, 0, 0, NULL },
  245.         { { 48, 96 }, { 164, 394 }, { 250, 500 }, { 180, 432 }, 7, 0, 0, NULL },
  246.         { { 152, 304 }, { 151, 362 }, { 299, 598 }, { 171, 410 }, 8, 1, 0, NULL },
  247.     };
  248.     static tRectile recopy_areas[24] = {
  249.         {
  250.             { 53, 106 },
  251.             { 17, 41 },
  252.             { 147, 294 },
  253.             { 35, 84 },
  254.         },
  255.         {
  256.             { 53, 106 },
  257.             { 39, 94 },
  258.             { 147, 294 },
  259.             { 57, 137 },
  260.         },
  261.         {
  262.             { 53, 106 },
  263.             { 59, 142 },
  264.             { 147, 294 },
  265.             { 77, 185 },
  266.         },
  267.         {
  268.             { 53, 106 },
  269.             { 80, 192 },
  270.             { 147, 294 },
  271.             { 98, 235 },
  272.         },
  273.         {
  274.             { 53, 106 },
  275.             { 101, 242 },
  276.             { 147, 294 },
  277.             { 119, 286 },
  278.         },
  279.         {
  280.             { 53, 106 },
  281.             { 122, 293 },
  282.             { 147, 294 },
  283.             { 140, 336 },
  284.         },
  285.         {
  286.             { 53, 106 },
  287.             { 143, 343 },
  288.             { 147, 294 },
  289.             { 161, 386 },
  290.         },
  291.         {
  292.             { 53, 106 },
  293.             { 164, 394 },
  294.             { 147, 294 },
  295.             { 182, 437 },
  296.         },
  297.         {
  298.             { 166, 332 },
  299.             { 17, 41 },
  300.             { 180, 360 },
  301.             { 35, 84 },
  302.         },
  303.         {
  304.             { 166, 332 },
  305.             { 39, 94 },
  306.             { 180, 360 },
  307.             { 57, 137 },
  308.         },
  309.         {
  310.             { 166, 332 },
  311.             { 59, 142 },
  312.             { 180, 360 },
  313.             { 77, 185 },
  314.         },
  315.         {
  316.             { 166, 332 },
  317.             { 80, 192 },
  318.             { 180, 360 },
  319.             { 98, 235 },
  320.         },
  321.         {
  322.             { 166, 332 },
  323.             { 101, 242 },
  324.             { 180, 360 },
  325.             { 119, 286 },
  326.         },
  327.         {
  328.             { 166, 332 },
  329.             { 122, 293 },
  330.             { 180, 360 },
  331.             { 140, 336 },
  332.         },
  333.         {
  334.             { 166, 332 },
  335.             { 143, 343 },
  336.             { 180, 360 },
  337.             { 161, 386 },
  338.         },
  339.         {
  340.             { 166, 332 },
  341.             { 164, 394 },
  342.             { 180, 360 },
  343.             { 182, 370 },
  344.         },
  345.         {
  346.             { 198, 396 },
  347.             { 17, 41 },
  348.             { 245, 490 },
  349.             { 35, 84 },
  350.         },
  351.         {
  352.             { 198, 396 },
  353.             { 39, 94 },
  354.             { 245, 490 },
  355.             { 57, 137 },
  356.         },
  357.         {
  358.             { 198, 396 },
  359.             { 59, 142 },
  360.             { 245, 490 },
  361.             { 77, 185 },
  362.         },
  363.         {
  364.             { 198, 396 },
  365.             { 80, 192 },
  366.             { 245, 490 },
  367.             { 98, 235 },
  368.         },
  369.         {
  370.             { 198, 396 },
  371.             { 101, 242 },
  372.             { 245, 490 },
  373.             { 119, 286 },
  374.         },
  375.         {
  376.             { 198, 396 },
  377.             { 122, 293 },
  378.             { 245, 490 },
  379.             { 140, 336 },
  380.         },
  381.         {
  382.             { 198, 396 },
  383.             { 143, 343 },
  384.             { 245, 490 },
  385.             { 161, 386 },
  386.         },
  387.         {
  388.             { 198, 396 },
  389.             { 164, 394 },
  390.             { 245, 490 },
  391.             { 182, 437 },
  392.         },
  393.     };
  394.     static tInterface_spec interface_spec = {
  395.         0,
  396.         71,
  397.         70,
  398.         0,
  399.         0,
  400.         0,
  401.         3,
  402.         { 1, 0 },
  403.         { -1, 8 },
  404.         { 8, 0 },
  405.         { 8, 7 },
  406.         { NULL, NULL },
  407.         { 1, 0 },
  408.         { 1, 0 },
  409.         { 8, 0 },
  410.         { 8, 7 },
  411.         { NULL, NULL },
  412.         { -1, -1 },
  413.         { -1, 0 },
  414.         { 0, 8 },
  415.         { 7, 8 },
  416.         { NULL, NULL },
  417.         { -1, -1 },
  418.         { 1, 0 },
  419.         { 0, 8 },
  420.         { 7, 8 },
  421.         { NULL, NULL },
  422.         { 1, 1 },
  423.         { NULL, NULL },
  424.         { 1, 1 },
  425.         { NULL, NULL },
  426.         NULL,
  427.         NULL,
  428.         0,
  429.         LoadStart,
  430.         NULL,
  431.         NULL,
  432.         1,
  433.         { 0, 0 },
  434.         NULL,
  435.         8,
  436.         1,
  437.         COUNT_OF(flicker_on),
  438.         flicker_on,
  439.         flicker_off,
  440.         push,
  441.         COUNT_OF(mouse_areas),
  442.         mouse_areas,
  443.         COUNT_OF(recopy_areas),
  444.         recopy_areas,
  445.     };
  446.     int result;
  447.     LOG_TRACE("()");
  448.  
  449.     if (harness_game_info.mode == eGame_carmageddon_demo || harness_game_info.mode == eGame_splatpack_demo || harness_game_info.mode == eGame_splatpack_xmas_demo) {
  450.         DoFeatureUnavailableInDemo();
  451.         return 0;
  452.     }
  453.  
  454.     if (gNet_mode == eNet_mode_none) {
  455.         if (!OriginalCarmaCDinDrive()) {
  456.             DoErrorInterface(kMiscString_PLEASE_INSERT_THE_CARMAGEDDON_CD);
  457.             return 0;
  458.         }
  459.         gProgram_state.loading = 1;
  460.         LoadSavedGames();
  461.         if (gGame_to_load >= 0) {
  462.             gProgram_state.last_slot = gGame_to_load;
  463.             gProgram_state.loaded = 1;
  464.             LoadTheGame(gGame_to_load);
  465.             gGame_to_load = -1;
  466.             gProgram_state.prog_status = eProg_game_starting;
  467.             DisposeSavedGames();
  468.             gProgram_state.loading = 0;
  469.             return 1;
  470.         } else {
  471.             result = DoInterfaceScreen(&interface_spec, gFaded_palette, gProgram_state.last_slot);
  472.             if (result != 8 && gSaved_games[result] == NULL) {
  473.                 result = 8;
  474.                 DRS3StartSound(gEffects_outlet, 3100);
  475.             }
  476.             if (result != 8) {
  477.                 FadePaletteDown();
  478.                 if (gProgram_state.racing) {
  479.                     gGame_to_load = result;
  480.                     gProgram_state.prog_status = eProg_idling;
  481.                 } else {
  482.                     gProgram_state.last_slot = result;
  483.                     gProgram_state.loaded = 1;
  484.                     LoadTheGame(result);
  485.                 }
  486.             } else {
  487.                 if (gProgram_state.racing) {
  488.                     FadePaletteDown();
  489.                 } else {
  490.                     RunFlic(72);
  491.                 }
  492.             }
  493.             gProgram_state.loading = 0;
  494.             DisposeSavedGames();
  495.             return result != 8;
  496.         }
  497.     } else {
  498.         SuspendPendingFlic();
  499.         DoErrorInterface(kMiscString_CannotSaveGameInNetworkPlay);
  500.         return 0;
  501.     }
  502. }
  503.  
  504. // IDA: void __usercall CorrectSaveByteOrdering(int pIndex@<EAX>)
  505. void CorrectSaveByteOrdering(int pIndex) {
  506.     int i;
  507.     LOG_TRACE("(%d)", pIndex);
  508.  
  509.     SWAP32_BE(gSaved_games[pIndex]->version);
  510.     SWAP32_BE(gSaved_games[pIndex]->rank);
  511.     SWAP32_BE(gSaved_games[pIndex]->credits);
  512.     SWAP32_BE(gSaved_games[pIndex]->skill_level);
  513.     SWAP32_BE(gSaved_games[pIndex]->frank_or_annitude);
  514.     SWAP32_BE(gSaved_games[pIndex]->game_completed);
  515.     SWAP32_BE(gSaved_games[pIndex]->current_race_index);
  516.     SWAP32_BE(gSaved_games[pIndex]->number_of_cars);
  517.     for (i = 0; i < COUNT_OF(gSaved_games[pIndex]->cars_available); i++) {
  518.         SWAP32_BE(gSaved_games[pIndex]->cars_available[i]);
  519.     }
  520.     SWAP32_BE(gSaved_games[pIndex]->current_car_index);
  521.     SWAP32_BE(gSaved_games[pIndex]->redo_race_index);
  522.     for (i = 0; i < COUNT_OF(gSaved_games[pIndex]->power_up_levels); i++) {
  523.         SWAP32_BE(gSaved_games[pIndex]->power_up_levels[i]);
  524.     }
  525. }
  526.  
  527. // IDA: void __usercall SaveTheGame(int pSlot_number@<EAX>)
  528. void SaveTheGame(int pSlot_number) {
  529.     tPath_name the_path;
  530.     FILE* f;
  531.     LOG_TRACE("(%d)", pSlot_number);
  532.  
  533.     gSaved_games[pSlot_number]->checksum = CalcLSChecksum(gSaved_games[pSlot_number]);
  534.     PathCat(the_path, gApplication_path, "SAVEGAME");
  535.     PathCat(the_path, the_path, "SAVEx");
  536.     the_path[strlen(the_path) - 1] = '0' + pSlot_number;
  537.     PDFileUnlock(the_path);
  538.     f = DRfopen(the_path, "wb");
  539.     if (f != NULL) {
  540.         CorrectSaveByteOrdering(pSlot_number);
  541.         fwrite(gSaved_games[pSlot_number], 1, sizeof(tSave_game), f);
  542.         fclose(f);
  543.         CorrectLoadByteOrdering(pSlot_number);
  544.     }
  545. }
  546.  
  547. // IDA: int __cdecl ConfirmMidGameSave()
  548. int ConfirmMidGameSave(void) {
  549.     static tFlicette flicker_on[2] = {
  550.         { 43, { 84, 168 }, { 124, 298 } },
  551.         { 43, { 181, 362 }, { 124, 298 } },
  552.     };
  553.     static tFlicette flicker_off[2] = {
  554.         { 42, { 84, 168 }, { 124, 298 } },
  555.         { 42, { 181, 362 }, { 124, 298 } },
  556.     };
  557.     static tFlicette push[2] = {
  558.         { 44, { 84, 168 }, { 124, 298 } },
  559.         { 45, { 181, 362 }, { 124, 298 } },
  560.     };
  561.     static tMouse_area mouse_areas[2] = {
  562.         { { 84, 168 }, { 124, 298 }, { 147, 294 }, { 144, 346 }, 0, 0, 0, NULL },
  563.         { { 181, 362 }, { 124, 298 }, { 244, 488 }, { 144, 346 }, 1, 0, 0, NULL },
  564.     };
  565.     static tInterface_spec interface_spec = {
  566.         0, 40, 0, 41, -1, -1, 0,
  567.         { -1, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, { NULL, NULL },
  568.         { -1, 0 }, { 1, 0 }, { 0, 0 }, { 1, 0 }, { NULL, NULL },
  569.         { -1, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { NULL, NULL },
  570.         { -1, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { NULL, NULL },
  571.         { 1, 1 }, { NULL, NULL }, { 1, 1 }, { NULL, NULL },
  572.         NULL, NULL, 0, NULL, NULL, NULL, 0, { 0, 0 },
  573.         NULL, 1, 1,
  574.         COUNT_OF(flicker_on), flicker_on, flicker_off, push,
  575.         COUNT_OF(mouse_areas), mouse_areas,
  576.         0, NULL
  577.     };
  578.     LOG_TRACE("()");
  579.  
  580.     return DoInterfaceScreen(&interface_spec, 0, 0) == 0;
  581. }
  582.  
  583. // IDA: void __usercall MakeSavedGame(tSave_game **pSave_record@<EAX>)
  584. void MakeSavedGame(tSave_game** pSave_record) {
  585.     int i;
  586.     LOG_TRACE("(%p)", pSave_record);
  587.  
  588.     if (*pSave_record == NULL) {
  589.         *pSave_record = BrMemCalloc(1, sizeof(tSave_game), kMem_new_save_game);
  590.     }
  591.     (*pSave_record)->skill_level = gProgram_state.skill_level;
  592.     (*pSave_record)->version = SAVEGAME_VERSION;
  593.     (*pSave_record)->frank_or_annitude = gProgram_state.frank_or_anniness;
  594.     (*pSave_record)->game_completed = gProgram_state.game_completed;
  595.     (*pSave_record)->current_race_index = gProgram_state.current_race_index;
  596.     for (i = 0; i < gNumber_of_races; i++) {
  597.         (*pSave_record)->race_info[i].been_there_done_that = gRace_list[i].been_there_done_that;
  598.     }
  599.     for (i = 0; i < gNumber_of_racers; i++) {
  600.         (*pSave_record)->opponent_info[i].dead = gOpponents[i].dead;
  601.     }
  602.     (*pSave_record)->credits = gProgram_state.credits;
  603.     (*pSave_record)->rank = gProgram_state.rank;
  604.     strcpy((*pSave_record)->car_name, gProgram_state.car_name);
  605.     strcpy((*pSave_record)->player_name[0], gProgram_state.player_name[0]);
  606.     strcpy((*pSave_record)->player_name[1], gProgram_state.player_name[1]);
  607.  
  608.     (*pSave_record)->number_of_cars = gProgram_state.number_of_cars;
  609.     (*pSave_record)->current_car_index = gProgram_state.current_car_index;
  610.     (*pSave_record)->redo_race_index = gProgram_state.redo_race_index;
  611.     for (i = 0; i < COUNT_OF(gProgram_state.cars_available); i++) {
  612.         (*pSave_record)->cars_available[i] = gProgram_state.cars_available[i];
  613.     }
  614.     for (i = 0; i < COUNT_OF(gProgram_state.current_car.power_up_levels); i++) {
  615.         (*pSave_record)->power_up_levels[i] = gProgram_state.current_car.power_up_levels[i];
  616.     }
  617. }
  618.  
  619. // IDA: void __cdecl SaveStart()
  620. void SaveStart(void) {
  621.     LOG_TRACE("()");
  622.  
  623.     StartRollingSaveNamesIn();
  624. }
  625.  
  626. // IDA: void __usercall GetSaveName(int pStarting_to_type@<EAX>, int pCurrent_choice@<EDX>, char *pString@<EBX>, int *pMax_length@<ECX>)
  627. void GetSaveName(int pStarting_to_type, int pCurrent_choice, char* pString, int* pMax_length) {
  628.     LOG_TRACE("(%d, %d, \"%s\", %p)", pStarting_to_type, pCurrent_choice, pString, pMax_length);
  629.  
  630.     if (gSaved_games[pCurrent_choice] != NULL) {
  631.         strcpy(pString, gSaved_games[pCurrent_choice]->slot_name);
  632.     } else {
  633.         if (pStarting_to_type) {
  634.             BlankSlot(pCurrent_choice, strlen(GetMiscString(kMiscString_Empty)), 12);
  635.             *pString = '\0';
  636.         } else {
  637.             strcpy(pString, GetMiscString(kMiscString_Empty));
  638.         }
  639.     }
  640.     *pMax_length = 12;
  641. }
  642.  
  643. // IDA: int __usercall SaveDone@<EAX>(int pCurrent_choice@<EAX>, int pCurrent_mode@<EDX>, int pGo_ahead@<EBX>, int pEscaped@<ECX>, int pTimed_out)
  644. int SaveDone(int pCurrent_choice, int pCurrent_mode, int pGo_ahead, int pEscaped, int pTimed_out) {
  645.     LOG_TRACE("(%d, %d, %d, %d, %d)", pCurrent_choice, pCurrent_mode, pGo_ahead, pEscaped, pTimed_out);
  646.  
  647.     if (!pEscaped && pCurrent_choice < 8) {
  648.         gProgram_state.last_slot = pCurrent_choice;
  649.         MakeSavedGame(&gSaved_games[pCurrent_choice]);
  650.         if (!gSave_allowed && gPre_race_saved_game != NULL) {
  651.             memcpy(gSaved_games[pCurrent_choice], gPre_race_saved_game, sizeof(tSave_game));
  652.         }
  653.         GetTypedName(gSaved_games[pCurrent_choice]->slot_name, 12);
  654.         gSaved_games[pCurrent_choice]->slot_name[12] = '\0';
  655.         SaveTheGame(pCurrent_choice);
  656.     }
  657.     return pCurrent_choice;
  658. }
  659.  
  660. // IDA: int __usercall SaveGoAhead@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
  661. int SaveGoAhead(int* pCurrent_choice, int* pCurrent_mode) {
  662.     char s1[256];
  663.     char s2[256];
  664.     LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);
  665.  
  666.     if (gTyping_slot != 0) {
  667.         sprintf(s1, VARLZEROINT, 2, gProgram_state.rank);
  668.         if (gSaved_games[*pCurrent_choice] == NULL) {
  669.             s2[0] = '\0';
  670.         } else {
  671.             sprintf(s2, VARLZEROINT, 2, gSaved_games[*pCurrent_choice]->rank);
  672.         }
  673.         ChangeTextTo(gCurrent_graf_data->save_slot_rank_x_offset,
  674.             gCurrent_graf_data->save_slot_y_offset + *pCurrent_choice * gCurrent_graf_data->rolling_letter_y_pitch,
  675.             s1, s2);
  676.         sprintf(s1, VARLZEROINT, 6, gProgram_state.credits);
  677.         if (gSaved_games[*pCurrent_choice] == NULL) {
  678.             s2[0] = '\0';
  679.         } else {
  680.             sprintf(s2, VARLZEROINT, 6, gSaved_games[*pCurrent_choice]->credits);
  681.         }
  682.         ChangeTextTo(gCurrent_graf_data->save_slot_credits_x_offset,
  683.             gCurrent_graf_data->save_slot_y_offset + *pCurrent_choice * gCurrent_graf_data->rolling_letter_y_pitch,
  684.             s1, s2);
  685.         gStarted_typing = 1;
  686.     }
  687.     return 1;
  688. }
  689.  
  690. // IDA: int __usercall SaveEscape@<EAX>(int *pCurrent_choice@<EAX>, int *pCurrent_mode@<EDX>)
  691. int SaveEscape(int* pCurrent_choice, int* pCurrent_mode) {
  692.     char s1[256];
  693.     char s2[256];
  694.     LOG_TRACE("(%p, %p)", pCurrent_choice, pCurrent_mode);
  695.  
  696.     if (gStarted_typing != 0) {
  697.         sprintf(s1, VARLZEROINT, 2, gProgram_state.rank);
  698.         if (gSaved_games[*pCurrent_choice] == NULL) {
  699.             s2[0] = '\0';
  700.         } else {
  701.             sprintf(s2, VARLZEROINT, 2, gSaved_games[*pCurrent_choice]->rank);
  702.         }
  703.         ChangeTextTo(gCurrent_graf_data->save_slot_rank_x_offset,
  704.             gCurrent_graf_data->save_slot_y_offset + *pCurrent_choice * gCurrent_graf_data->rolling_letter_y_pitch,
  705.             s2, s1);
  706.         sprintf(s1, VARLZEROINT, 6, gProgram_state.credits);
  707.         if (gSaved_games[*pCurrent_choice] == NULL) {
  708.             s2[0] = '\0';
  709.         } else {
  710.             sprintf(s2, VARLZEROINT, 6, gSaved_games[*pCurrent_choice]->credits);
  711.         }
  712.         ChangeTextTo(gCurrent_graf_data->save_slot_credits_x_offset,
  713.             gCurrent_graf_data->save_slot_y_offset + *pCurrent_choice * gCurrent_graf_data->rolling_letter_y_pitch,
  714.             s2, s1);
  715.         gStarted_typing = 0;
  716.     }
  717.     return 1;
  718. }
  719.  
  720. // IDA: int __usercall SaveGameInterface@<EAX>(int pDefault_choice@<EAX>)
  721. int SaveGameInterface(int pDefault_choice) {
  722.     static tFlicette flicker_on[9] = {
  723.         { 74, { 47, 94 }, { 23, 55 } },
  724.         { 74, { 47, 94 }, { 44, 106 } },
  725.         { 74, { 47, 94 }, { 65, 156 } },
  726.         { 74, { 47, 94 }, { 86, 206 } },
  727.         { 74, { 47, 94 }, { 107, 257 } },
  728.         { 74, { 47, 94 }, { 128, 307 } },
  729.         { 74, { 47, 94 }, { 149, 358 } },
  730.         { 74, { 47, 94 }, { 170, 408 } },
  731.         { 57, { 255, 510 }, { 151, 362 } },
  732.     };
  733.     static tFlicette flicker_off[9] = {
  734.         { 73, { 47, 94 }, { 23, 55 } },
  735.         { 73, { 47, 94 }, { 44, 106 } },
  736.         { 73, { 47, 94 }, { 65, 156 } },
  737.         { 73, { 47, 94 }, { 86, 206 } },
  738.         { 73, { 47, 94 }, { 107, 257 } },
  739.         { 73, { 47, 94 }, { 128, 307 } },
  740.         { 73, { 47, 94 }, { 149, 358 } },
  741.         { 73, { 47, 94 }, { 170, 408 } },
  742.         { 56, { 255, 510 }, { 151, 362 } },
  743.     };
  744.     static tFlicette push[9] = {
  745.         { 74, { 47, 94 }, { 23, 55 } },
  746.         { 74, { 47, 94 }, { 44, 106 } },
  747.         { 74, { 47, 94 }, { 65, 156 } },
  748.         { 74, { 47, 94 }, { 86, 206 } },
  749.         { 74, { 47, 94 }, { 107, 257 } },
  750.         { 74, { 47, 94 }, { 128, 307 } },
  751.         { 74, { 47, 94 }, { 149, 358 } },
  752.         { 74, { 47, 94 }, { 170, 408 } },
  753.         { 59, { 255, 510 }, { 151, 362 } },
  754.     };
  755.     static tMouse_area mouse_areas[9] = {
  756.         { { 48, 96 }, { 17, 41 }, { 250, 500 }, { 33, 79 }, 0, 0, 0, NULL },
  757.         { { 48, 96 }, { 39, 94 }, { 250, 500 }, { 55, 132 }, 1, 0, 0, NULL },
  758.         { { 48, 96 }, { 59, 142 }, { 250, 500 }, { 75, 180 }, 2, 0, 0, NULL },
  759.         { { 48, 96 }, { 80, 192 }, { 250, 500 }, { 96, 230 }, 3, 0, 0, NULL },
  760.         { { 48, 96 }, { 101, 242 }, { 250, 500 }, { 117, 281 }, 4, 0, 0, NULL },
  761.         { { 48, 96 }, { 122, 293 }, { 250, 500 }, { 138, 331 }, 5, 0, 0, NULL },
  762.         { { 48, 96 }, { 143, 343 }, { 250, 500 }, { 159, 382 }, 6, 0, 0, NULL },
  763.         { { 48, 96 }, { 164, 394 }, { 250, 500 }, { 180, 432 }, 7, 0, 0, NULL },
  764.         { { 152, 304 }, { 151, 362 }, { 299, 598 }, { 171, 410 }, 8, 1, 0, NULL },
  765.     };
  766.     static tRectile recopy_areas[24] = {
  767.         {
  768.             { 53, 106 },
  769.             { 17, 41 },
  770.             { 147, 294 },
  771.             { 35, 84 },
  772.         },
  773.         {
  774.             { 53, 106 },
  775.             { 39, 94 },
  776.             { 147, 294 },
  777.             { 57, 137 },
  778.         },
  779.         {
  780.             { 53, 106 },
  781.             { 59, 142 },
  782.             { 147, 294 },
  783.             { 77, 185 },
  784.         },
  785.         {
  786.             { 53, 106 },
  787.             { 80, 192 },
  788.             { 147, 294 },
  789.             { 98, 235 },
  790.         },
  791.         {
  792.             { 53, 106 },
  793.             { 101, 242 },
  794.             { 147, 294 },
  795.             { 119, 286 },
  796.         },
  797.         {
  798.             { 53, 106 },
  799.             { 122, 293 },
  800.             { 147, 294 },
  801.             { 140, 336 },
  802.         },
  803.         {
  804.             { 53, 106 },
  805.             { 143, 343 },
  806.             { 147, 294 },
  807.             { 161, 386 },
  808.         },
  809.         {
  810.             { 53, 106 },
  811.             { 164, 394 },
  812.             { 147, 294 },
  813.             { 182, 437 },
  814.         },
  815.         {
  816.             { 166, 332 },
  817.             { 17, 41 },
  818.             { 180, 360 },
  819.             { 35, 84 },
  820.         },
  821.         {
  822.             { 166, 332 },
  823.             { 39, 94 },
  824.             { 180, 360 },
  825.             { 57, 137 },
  826.         },
  827.         {
  828.             { 166, 332 },
  829.             { 59, 142 },
  830.             { 180, 360 },
  831.             { 77, 185 },
  832.         },
  833.         {
  834.             { 166, 332 },
  835.             { 80, 192 },
  836.             { 180, 360 },
  837.             { 98, 235 },
  838.         },
  839.         {
  840.             { 166, 332 },
  841.             { 101, 242 },
  842.             { 180, 360 },
  843.             { 119, 286 },
  844.         },
  845.         {
  846.             { 166, 332 },
  847.             { 122, 293 },
  848.             { 180, 360 },
  849.             { 140, 336 },
  850.         },
  851.         {
  852.             { 166, 332 },
  853.             { 143, 343 },
  854.             { 180, 360 },
  855.             { 161, 386 },
  856.         },
  857.         {
  858.             { 166, 332 },
  859.             { 164, 394 },
  860.             { 180, 360 },
  861.             { 182, 437 },
  862.         },
  863.         {
  864.             { 198, 396 },
  865.             { 17, 41 },
  866.             { 245, 490 },
  867.             { 35, 84 },
  868.         },
  869.         {
  870.             { 198, 396 },
  871.             { 39, 94 },
  872.             { 245, 490 },
  873.             { 57, 137 },
  874.         },
  875.         {
  876.             { 198, 396 },
  877.             { 59, 142 },
  878.             { 245, 490 },
  879.             { 77, 185 },
  880.         },
  881.         {
  882.             { 198, 396 },
  883.             { 80, 192 },
  884.             { 245, 490 },
  885.             { 98, 235 },
  886.         },
  887.         {
  888.             { 198, 396 },
  889.             { 101, 242 },
  890.             { 245, 490 },
  891.             { 119, 286 },
  892.         },
  893.         {
  894.             { 198, 396 },
  895.             { 122, 293 },
  896.             { 245, 490 },
  897.             { 140, 336 },
  898.         },
  899.         {
  900.             { 198, 396 },
  901.             { 143, 343 },
  902.             { 245, 490 },
  903.             { 161, 386 },
  904.         },
  905.         {
  906.             { 198, 396 },
  907.             { 164, 394 },
  908.             { 245, 490 },
  909.             { 182, 437 },
  910.         },
  911.     };
  912.     static tInterface_spec interface_spec = {
  913.         0, 50, 0, 0, 0, 0, 2,
  914.         { 1, 0 }, { -1, 8 }, { 8, 0 }, { 8, 7 }, { NULL, NULL },
  915.         { 1, 0 }, { 1, 0 }, { 8, 0 }, { 8, 7 }, { NULL, NULL },
  916.         { -1, -1 }, { -1, 0 }, { 0, 8 }, { 7, 8 }, { NULL, NULL },
  917.         { -1, -1 }, { 1, 0 }, { 0, 8 }, { 7, 8 }, { NULL, NULL },
  918.         { 1, 1 }, { SaveGoAhead, NULL },
  919.         { 1, 1 }, { SaveEscape, NULL },
  920.         NULL, NULL, 0, SaveStart, NULL, SaveDone, 1,
  921.         { 1, 0 }, GetSaveName, 8, 1,
  922.         COUNT_OF(flicker_on), flicker_on, flicker_off, push,
  923.         COUNT_OF(mouse_areas), mouse_areas,
  924.         COUNT_OF(recopy_areas), recopy_areas
  925.     };
  926.     LOG_TRACE("(%d)", pDefault_choice);
  927.  
  928.     gStarted_typing = 0;
  929.     return DoInterfaceScreen(&interface_spec, 0, pDefault_choice) < 8;
  930. }
  931.  
  932. // IDA: void __usercall DoSaveGame(int pSave_allowed@<EAX>)
  933. void DoSaveGame(int pSave_allowed) {
  934.     LOG_TRACE("()");
  935.  
  936.     if (harness_game_info.mode == eGame_carmageddon_demo || harness_game_info.mode == eGame_splatpack_demo || harness_game_info.mode == eGame_splatpack_xmas_demo) {
  937.         DoFeatureUnavailableInDemo();
  938.         return;
  939.     }
  940.  
  941.     if (gNet_mode == eNet_mode_none) {
  942.         DRS3StopOutletSound(gEffects_outlet);
  943.         gProgram_state.saving = 1;
  944.         gSave_allowed = pSave_allowed;
  945.         LoadSavedGames();
  946.         LoadFont(kFont_TYPEABLE);
  947.         if (!pSave_allowed && !ConfirmMidGameSave()) {
  948.             gProgram_state.saving = 0;
  949.             return;
  950.         }
  951.         SaveGameInterface(gProgram_state.last_slot);
  952.         if (pSave_allowed) {
  953.             RunFlic(51);
  954.         } else {
  955.             FadePaletteDown();
  956.         }
  957.         DisposeSavedGames();
  958.         gProgram_state.saving = 0;
  959.     } else {
  960.         SuspendPendingFlic();
  961.         DoErrorInterface(kMiscString_CannotSaveGameInNetworkPlay);
  962.     }
  963. }
  964.