Subversion Repositories Games.Carmageddon

Rev

Rev 11 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

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